dunstan 0.1.12 → 0.1.14
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 +90 -4
- package/dist/dunstan.mjs +1634 -383
- package/package.json +1 -1
package/dist/dunstan.mjs
CHANGED
|
@@ -101,7 +101,7 @@ var require_code = __commonJS({
|
|
|
101
101
|
}
|
|
102
102
|
exports._ = _;
|
|
103
103
|
var plus = new _Code("+");
|
|
104
|
-
function
|
|
104
|
+
function str3(strs, ...args) {
|
|
105
105
|
const expr = [safeStringify(strs[0])];
|
|
106
106
|
let i = 0;
|
|
107
107
|
while (i < args.length) {
|
|
@@ -112,7 +112,7 @@ var require_code = __commonJS({
|
|
|
112
112
|
optimize(expr);
|
|
113
113
|
return new _Code(expr);
|
|
114
114
|
}
|
|
115
|
-
exports.str =
|
|
115
|
+
exports.str = str3;
|
|
116
116
|
function addCodeArg(code, arg) {
|
|
117
117
|
if (arg instanceof _Code)
|
|
118
118
|
code.push(...arg._items);
|
|
@@ -155,7 +155,7 @@ var require_code = __commonJS({
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
function strConcat(c1, c2) {
|
|
158
|
-
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 :
|
|
158
|
+
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str3`${c1}${c2}`;
|
|
159
159
|
}
|
|
160
160
|
exports.strConcat = strConcat;
|
|
161
161
|
function interpolate(x) {
|
|
@@ -246,13 +246,13 @@ var require_scope = __commonJS({
|
|
|
246
246
|
}
|
|
247
247
|
};
|
|
248
248
|
exports.ValueScopeName = ValueScopeName;
|
|
249
|
-
var
|
|
249
|
+
var line2 = (0, code_1._)`\n`;
|
|
250
250
|
var ValueScope = class extends Scope {
|
|
251
251
|
constructor(opts) {
|
|
252
252
|
super(opts);
|
|
253
253
|
this._values = {};
|
|
254
254
|
this._scope = opts.scope;
|
|
255
|
-
this.opts = { ...opts, _n: opts.lines ?
|
|
255
|
+
this.opts = { ...opts, _n: opts.lines ? line2 : code_1.nil };
|
|
256
256
|
}
|
|
257
257
|
get() {
|
|
258
258
|
return this._scope;
|
|
@@ -1117,22 +1117,22 @@ var require_util = __commonJS({
|
|
|
1117
1117
|
return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
|
|
1118
1118
|
}
|
|
1119
1119
|
exports.schemaRefOrVal = schemaRefOrVal;
|
|
1120
|
-
function unescapeFragment(
|
|
1121
|
-
return unescapeJsonPointer(decodeURIComponent(
|
|
1120
|
+
function unescapeFragment(str3) {
|
|
1121
|
+
return unescapeJsonPointer(decodeURIComponent(str3));
|
|
1122
1122
|
}
|
|
1123
1123
|
exports.unescapeFragment = unescapeFragment;
|
|
1124
|
-
function escapeFragment(
|
|
1125
|
-
return encodeURIComponent(escapeJsonPointer(
|
|
1124
|
+
function escapeFragment(str3) {
|
|
1125
|
+
return encodeURIComponent(escapeJsonPointer(str3));
|
|
1126
1126
|
}
|
|
1127
1127
|
exports.escapeFragment = escapeFragment;
|
|
1128
|
-
function escapeJsonPointer(
|
|
1129
|
-
if (typeof
|
|
1130
|
-
return `${
|
|
1131
|
-
return
|
|
1128
|
+
function escapeJsonPointer(str3) {
|
|
1129
|
+
if (typeof str3 == "number")
|
|
1130
|
+
return `${str3}`;
|
|
1131
|
+
return str3.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1132
1132
|
}
|
|
1133
1133
|
exports.escapeJsonPointer = escapeJsonPointer;
|
|
1134
|
-
function unescapeJsonPointer(
|
|
1135
|
-
return
|
|
1134
|
+
function unescapeJsonPointer(str3) {
|
|
1135
|
+
return str3.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
1136
1136
|
}
|
|
1137
1137
|
exports.unescapeJsonPointer = unescapeJsonPointer;
|
|
1138
1138
|
function eachItem(xs, f) {
|
|
@@ -2157,8 +2157,8 @@ var require_json_schema_traverse = __commonJS({
|
|
|
2157
2157
|
post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
|
|
2158
2158
|
}
|
|
2159
2159
|
}
|
|
2160
|
-
function escapeJsonPtr(
|
|
2161
|
-
return
|
|
2160
|
+
function escapeJsonPtr(str3) {
|
|
2161
|
+
return str3.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2162
2162
|
}
|
|
2163
2163
|
}
|
|
2164
2164
|
});
|
|
@@ -2984,7 +2984,7 @@ var require_compile = __commonJS({
|
|
|
2984
2984
|
const schOrFunc = root.refs[ref];
|
|
2985
2985
|
if (schOrFunc)
|
|
2986
2986
|
return schOrFunc;
|
|
2987
|
-
let _sch =
|
|
2987
|
+
let _sch = resolve5.call(this, root, ref);
|
|
2988
2988
|
if (_sch === void 0) {
|
|
2989
2989
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2990
2990
|
const { schemaId: schemaId2 } = this.opts;
|
|
@@ -3011,7 +3011,7 @@ var require_compile = __commonJS({
|
|
|
3011
3011
|
function sameSchemaEnv(s1, s2) {
|
|
3012
3012
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3013
3013
|
}
|
|
3014
|
-
function
|
|
3014
|
+
function resolve5(root, ref) {
|
|
3015
3015
|
let sch;
|
|
3016
3016
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3017
3017
|
ref = sch;
|
|
@@ -3255,10 +3255,10 @@ var require_utils = __commonJS({
|
|
|
3255
3255
|
isIPV6: true
|
|
3256
3256
|
};
|
|
3257
3257
|
}
|
|
3258
|
-
function findToken(
|
|
3258
|
+
function findToken(str3, token) {
|
|
3259
3259
|
let ind = 0;
|
|
3260
|
-
for (let i = 0; i <
|
|
3261
|
-
if (
|
|
3260
|
+
for (let i = 0; i < str3.length; i++) {
|
|
3261
|
+
if (str3[i] === token) ind++;
|
|
3262
3262
|
}
|
|
3263
3263
|
return ind;
|
|
3264
3264
|
}
|
|
@@ -3841,7 +3841,7 @@ var require_fast_uri = __commonJS({
|
|
|
3841
3841
|
}
|
|
3842
3842
|
return uri;
|
|
3843
3843
|
}
|
|
3844
|
-
function
|
|
3844
|
+
function resolve5(baseURI, relativeURI, options) {
|
|
3845
3845
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3846
3846
|
const {
|
|
3847
3847
|
parsed: baseParsed,
|
|
@@ -4210,7 +4210,7 @@ var require_fast_uri = __commonJS({
|
|
|
4210
4210
|
var fastUri = {
|
|
4211
4211
|
SCHEMES,
|
|
4212
4212
|
normalize,
|
|
4213
|
-
resolve:
|
|
4213
|
+
resolve: resolve5,
|
|
4214
4214
|
resolveComponent,
|
|
4215
4215
|
equal,
|
|
4216
4216
|
serialize: serialize2,
|
|
@@ -4272,7 +4272,7 @@ var require_core = __commonJS({
|
|
|
4272
4272
|
var util_1 = require_util();
|
|
4273
4273
|
var $dataRefSchema = require_data();
|
|
4274
4274
|
var uri_1 = require_uri();
|
|
4275
|
-
var defaultRegExp = (
|
|
4275
|
+
var defaultRegExp = (str3, flags) => new RegExp(str3, flags);
|
|
4276
4276
|
defaultRegExp.code = "new RegExp";
|
|
4277
4277
|
var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"];
|
|
4278
4278
|
var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -4625,7 +4625,7 @@ var require_core = __commonJS({
|
|
|
4625
4625
|
errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
|
|
4626
4626
|
if (!errors || errors.length === 0)
|
|
4627
4627
|
return "No errors";
|
|
4628
|
-
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((
|
|
4628
|
+
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
|
|
4629
4629
|
}
|
|
4630
4630
|
$dataMetaSchema(metaSchema, keywordsJsonPointers) {
|
|
4631
4631
|
const rules = this.RULES.all;
|
|
@@ -5067,16 +5067,16 @@ var require_ucs2length = __commonJS({
|
|
|
5067
5067
|
"node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/runtime/ucs2length.js"(exports) {
|
|
5068
5068
|
"use strict";
|
|
5069
5069
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5070
|
-
function ucs2length(
|
|
5071
|
-
const len =
|
|
5070
|
+
function ucs2length(str3) {
|
|
5071
|
+
const len = str3.length;
|
|
5072
5072
|
let length = 0;
|
|
5073
5073
|
let pos = 0;
|
|
5074
5074
|
let value;
|
|
5075
5075
|
while (pos < len) {
|
|
5076
5076
|
length++;
|
|
5077
|
-
value =
|
|
5077
|
+
value = str3.charCodeAt(pos++);
|
|
5078
5078
|
if (value >= 55296 && value <= 56319 && pos < len) {
|
|
5079
|
-
value =
|
|
5079
|
+
value = str3.charCodeAt(pos);
|
|
5080
5080
|
if ((value & 64512) === 56320)
|
|
5081
5081
|
pos++;
|
|
5082
5082
|
}
|
|
@@ -7534,8 +7534,8 @@ var require_formats = __commonJS({
|
|
|
7534
7534
|
}
|
|
7535
7535
|
var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
7536
7536
|
var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
7537
|
-
function date5(
|
|
7538
|
-
const matches = DATE.exec(
|
|
7537
|
+
function date5(str3) {
|
|
7538
|
+
const matches = DATE.exec(str3);
|
|
7539
7539
|
if (!matches)
|
|
7540
7540
|
return false;
|
|
7541
7541
|
const year = +matches[1];
|
|
@@ -7554,8 +7554,8 @@ var require_formats = __commonJS({
|
|
|
7554
7554
|
}
|
|
7555
7555
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
7556
7556
|
function getTime(strictTimeZone) {
|
|
7557
|
-
return function time3(
|
|
7558
|
-
const matches = TIME.exec(
|
|
7557
|
+
return function time3(str3) {
|
|
7558
|
+
const matches = TIME.exec(str3);
|
|
7559
7559
|
if (!matches)
|
|
7560
7560
|
return false;
|
|
7561
7561
|
const hr = +matches[1];
|
|
@@ -7601,8 +7601,8 @@ var require_formats = __commonJS({
|
|
|
7601
7601
|
var DATE_TIME_SEPARATOR = /t|\s/i;
|
|
7602
7602
|
function getDateTime(strictTimeZone) {
|
|
7603
7603
|
const time3 = getTime(strictTimeZone);
|
|
7604
|
-
return function date_time(
|
|
7605
|
-
const dateTime =
|
|
7604
|
+
return function date_time(str3) {
|
|
7605
|
+
const dateTime = str3.split(DATE_TIME_SEPARATOR);
|
|
7606
7606
|
return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
|
|
7607
7607
|
};
|
|
7608
7608
|
}
|
|
@@ -7627,13 +7627,13 @@ var require_formats = __commonJS({
|
|
|
7627
7627
|
}
|
|
7628
7628
|
var NOT_URI_FRAGMENT = /\/|:/;
|
|
7629
7629
|
var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
|
|
7630
|
-
function uri(
|
|
7631
|
-
return NOT_URI_FRAGMENT.test(
|
|
7630
|
+
function uri(str3) {
|
|
7631
|
+
return NOT_URI_FRAGMENT.test(str3) && URI.test(str3);
|
|
7632
7632
|
}
|
|
7633
7633
|
var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
|
|
7634
|
-
function byte(
|
|
7634
|
+
function byte(str3) {
|
|
7635
7635
|
BYTE.lastIndex = 0;
|
|
7636
|
-
return BYTE.test(
|
|
7636
|
+
return BYTE.test(str3);
|
|
7637
7637
|
}
|
|
7638
7638
|
var MIN_INT32 = -(2 ** 31);
|
|
7639
7639
|
var MAX_INT32 = 2 ** 31 - 1;
|
|
@@ -7647,11 +7647,11 @@ var require_formats = __commonJS({
|
|
|
7647
7647
|
return true;
|
|
7648
7648
|
}
|
|
7649
7649
|
var Z_ANCHOR = /[^\\]\\Z/;
|
|
7650
|
-
function regex(
|
|
7651
|
-
if (Z_ANCHOR.test(
|
|
7650
|
+
function regex(str3) {
|
|
7651
|
+
if (Z_ANCHOR.test(str3))
|
|
7652
7652
|
return false;
|
|
7653
7653
|
try {
|
|
7654
|
-
new RegExp(
|
|
7654
|
+
new RegExp(str3);
|
|
7655
7655
|
return true;
|
|
7656
7656
|
} catch (e) {
|
|
7657
7657
|
return false;
|
|
@@ -8024,13 +8024,13 @@ var require_dist = __commonJS({
|
|
|
8024
8024
|
});
|
|
8025
8025
|
|
|
8026
8026
|
// src/cli/bin.ts
|
|
8027
|
-
import { readFileSync as
|
|
8027
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
8028
8028
|
|
|
8029
8029
|
// src/spec/jcs.ts
|
|
8030
8030
|
import { createHash } from "node:crypto";
|
|
8031
8031
|
|
|
8032
8032
|
// src/spec/constants.ts
|
|
8033
|
-
var SPEC_VERSION = "0.1.
|
|
8033
|
+
var SPEC_VERSION = "0.1.9";
|
|
8034
8034
|
var BLOCK_VERSION = "0.1";
|
|
8035
8035
|
var SUPPORTED_BLOCK_VERSIONS = [BLOCK_VERSION];
|
|
8036
8036
|
var BLOCK_INFO_STRING = "dunstan-handback";
|
|
@@ -8057,11 +8057,11 @@ var NUMBER = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
|
|
|
8057
8057
|
function escapePointerToken(token) {
|
|
8058
8058
|
return token.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
8059
8059
|
}
|
|
8060
|
-
function isWellFormedUtf16(
|
|
8061
|
-
for (let i = 0; i <
|
|
8062
|
-
const unit =
|
|
8060
|
+
function isWellFormedUtf16(text2) {
|
|
8061
|
+
for (let i = 0; i < text2.length; i++) {
|
|
8062
|
+
const unit = text2.charCodeAt(i);
|
|
8063
8063
|
if (unit >= 55296 && unit <= 56319) {
|
|
8064
|
-
const next =
|
|
8064
|
+
const next = text2.charCodeAt(i + 1);
|
|
8065
8065
|
if (!(next >= 56320 && next <= 57343)) return false;
|
|
8066
8066
|
i++;
|
|
8067
8067
|
} else if (unit >= 56320 && unit <= 57343) {
|
|
@@ -8070,14 +8070,14 @@ function isWellFormedUtf16(text) {
|
|
|
8070
8070
|
}
|
|
8071
8071
|
return true;
|
|
8072
8072
|
}
|
|
8073
|
-
function parseStrictJson(
|
|
8073
|
+
function parseStrictJson(text2) {
|
|
8074
8074
|
let pos = 0;
|
|
8075
8075
|
const fail2 = (message) => {
|
|
8076
8076
|
throw new JsonReadError("invalid_json", `${message} at offset ${pos}`);
|
|
8077
8077
|
};
|
|
8078
8078
|
const skipWhitespace = () => {
|
|
8079
|
-
while (pos <
|
|
8080
|
-
const c =
|
|
8079
|
+
while (pos < text2.length) {
|
|
8080
|
+
const c = text2[pos];
|
|
8081
8081
|
if (c === " " || c === " " || c === "\n" || c === "\r") pos++;
|
|
8082
8082
|
else break;
|
|
8083
8083
|
}
|
|
@@ -8086,8 +8086,8 @@ function parseStrictJson(text) {
|
|
|
8086
8086
|
pos++;
|
|
8087
8087
|
let out = "";
|
|
8088
8088
|
while (true) {
|
|
8089
|
-
if (pos >=
|
|
8090
|
-
const c =
|
|
8089
|
+
if (pos >= text2.length) fail2("unterminated string");
|
|
8090
|
+
const c = text2[pos];
|
|
8091
8091
|
const unit = c.charCodeAt(0);
|
|
8092
8092
|
if (c === '"') {
|
|
8093
8093
|
pos++;
|
|
@@ -8099,7 +8099,7 @@ function parseStrictJson(text) {
|
|
|
8099
8099
|
pos++;
|
|
8100
8100
|
continue;
|
|
8101
8101
|
}
|
|
8102
|
-
const e =
|
|
8102
|
+
const e = text2[pos + 1];
|
|
8103
8103
|
pos += 2;
|
|
8104
8104
|
switch (e) {
|
|
8105
8105
|
case '"':
|
|
@@ -8127,7 +8127,7 @@ function parseStrictJson(text) {
|
|
|
8127
8127
|
out += " ";
|
|
8128
8128
|
break;
|
|
8129
8129
|
case "u": {
|
|
8130
|
-
const hex3 =
|
|
8130
|
+
const hex3 = text2.slice(pos, pos + 4);
|
|
8131
8131
|
if (!/^[0-9a-fA-F]{4}$/.test(hex3)) fail2("bad \\u escape");
|
|
8132
8132
|
out += String.fromCharCode(Number.parseInt(hex3, 16));
|
|
8133
8133
|
pos += 4;
|
|
@@ -8143,26 +8143,26 @@ function parseStrictJson(text) {
|
|
|
8143
8143
|
};
|
|
8144
8144
|
const readValue = (pointer, depth) => {
|
|
8145
8145
|
skipWhitespace();
|
|
8146
|
-
const c =
|
|
8146
|
+
const c = text2[pos];
|
|
8147
8147
|
if (c === "{" || c === "[") {
|
|
8148
8148
|
if (depth + 1 > MAX_BLOCK_DEPTH) fail2(`nesting deeper than ${MAX_BLOCK_DEPTH}`);
|
|
8149
8149
|
return c === "{" ? readObject(pointer, depth + 1) : readArray(pointer, depth + 1);
|
|
8150
8150
|
}
|
|
8151
8151
|
if (c === '"') return readString();
|
|
8152
|
-
if (
|
|
8152
|
+
if (text2.startsWith("true", pos)) {
|
|
8153
8153
|
pos += 4;
|
|
8154
8154
|
return true;
|
|
8155
8155
|
}
|
|
8156
|
-
if (
|
|
8156
|
+
if (text2.startsWith("false", pos)) {
|
|
8157
8157
|
pos += 5;
|
|
8158
8158
|
return false;
|
|
8159
8159
|
}
|
|
8160
|
-
if (
|
|
8160
|
+
if (text2.startsWith("null", pos)) {
|
|
8161
8161
|
pos += 4;
|
|
8162
8162
|
return null;
|
|
8163
8163
|
}
|
|
8164
8164
|
NUMBER.lastIndex = pos;
|
|
8165
|
-
const match = NUMBER.exec(
|
|
8165
|
+
const match = NUMBER.exec(text2);
|
|
8166
8166
|
if (match === null) return fail2("unexpected character");
|
|
8167
8167
|
pos += match[0].length;
|
|
8168
8168
|
const n = Number(match[0]);
|
|
@@ -8173,14 +8173,14 @@ function parseStrictJson(text) {
|
|
|
8173
8173
|
pos++;
|
|
8174
8174
|
const out = [];
|
|
8175
8175
|
skipWhitespace();
|
|
8176
|
-
if (
|
|
8176
|
+
if (text2[pos] === "]") {
|
|
8177
8177
|
pos++;
|
|
8178
8178
|
return out;
|
|
8179
8179
|
}
|
|
8180
8180
|
while (true) {
|
|
8181
8181
|
out.push(readValue(`${pointer}/${out.length}`, depth));
|
|
8182
8182
|
skipWhitespace();
|
|
8183
|
-
const c =
|
|
8183
|
+
const c = text2[pos];
|
|
8184
8184
|
pos++;
|
|
8185
8185
|
if (c === "]") return out;
|
|
8186
8186
|
if (c !== ",") {
|
|
@@ -8194,13 +8194,13 @@ function parseStrictJson(text) {
|
|
|
8194
8194
|
const entries = [];
|
|
8195
8195
|
const seen = /* @__PURE__ */ new Set();
|
|
8196
8196
|
skipWhitespace();
|
|
8197
|
-
if (
|
|
8197
|
+
if (text2[pos] === "}") {
|
|
8198
8198
|
pos++;
|
|
8199
8199
|
return {};
|
|
8200
8200
|
}
|
|
8201
8201
|
while (true) {
|
|
8202
8202
|
skipWhitespace();
|
|
8203
|
-
if (
|
|
8203
|
+
if (text2[pos] !== '"') fail2("expected a member name");
|
|
8204
8204
|
const key = readString();
|
|
8205
8205
|
const memberPointer = `${pointer}/${escapePointerToken(key)}`;
|
|
8206
8206
|
if (seen.has(key)) {
|
|
@@ -8212,11 +8212,11 @@ function parseStrictJson(text) {
|
|
|
8212
8212
|
}
|
|
8213
8213
|
seen.add(key);
|
|
8214
8214
|
skipWhitespace();
|
|
8215
|
-
if (
|
|
8215
|
+
if (text2[pos] !== ":") fail2("expected ':'");
|
|
8216
8216
|
pos++;
|
|
8217
8217
|
entries.push([key, readValue(memberPointer, depth)]);
|
|
8218
8218
|
skipWhitespace();
|
|
8219
|
-
const c =
|
|
8219
|
+
const c = text2[pos];
|
|
8220
8220
|
pos++;
|
|
8221
8221
|
if (c === "}") return Object.fromEntries(entries);
|
|
8222
8222
|
if (c !== ",") {
|
|
@@ -8227,7 +8227,7 @@ function parseStrictJson(text) {
|
|
|
8227
8227
|
};
|
|
8228
8228
|
const value = readValue("", 0);
|
|
8229
8229
|
skipWhitespace();
|
|
8230
|
-
if (pos !==
|
|
8230
|
+
if (pos !== text2.length) fail2("unexpected content after the JSON value");
|
|
8231
8231
|
return value;
|
|
8232
8232
|
}
|
|
8233
8233
|
|
|
@@ -8421,10 +8421,10 @@ function findReference(evidence, kind, ref) {
|
|
|
8421
8421
|
(r) => r.kind === kind && (kind === "issue" ? sameIssue(r.ref, ref) : r.ref === ref)
|
|
8422
8422
|
);
|
|
8423
8423
|
}
|
|
8424
|
-
function closedBy(reference, field, evidence, repository,
|
|
8424
|
+
function closedBy(reference, field, evidence, repository, at2, observed) {
|
|
8425
8425
|
const pr = evidence.pullRequest;
|
|
8426
8426
|
const section = evidence.closeEvents;
|
|
8427
|
-
const target = section?.targets.find((t) => sameIssue(t.ref,
|
|
8427
|
+
const target = section?.targets.find((t) => sameIssue(t.ref, at2));
|
|
8428
8428
|
const side = section?.pullRequest;
|
|
8429
8429
|
let unread2;
|
|
8430
8430
|
if (section === void 0 || target === void 0) unread2 = absentSection("closeEvents");
|
|
@@ -8468,10 +8468,10 @@ function closedBy(reference, field, evidence, repository, at, observed) {
|
|
|
8468
8468
|
"not_closing"
|
|
8469
8469
|
);
|
|
8470
8470
|
}
|
|
8471
|
-
function closesBlock(issue2,
|
|
8471
|
+
function closesBlock(issue2, headCommit2) {
|
|
8472
8472
|
return {
|
|
8473
8473
|
dunstan: BLOCK_VERSION,
|
|
8474
|
-
headCommit,
|
|
8474
|
+
headCommit: headCommit2,
|
|
8475
8475
|
filesChanged: [],
|
|
8476
8476
|
references: [{ issue: issue2, relation: "closes" }]
|
|
8477
8477
|
};
|
|
@@ -8559,11 +8559,10 @@ function checkTests(block, evidence) {
|
|
|
8559
8559
|
const countField = `/tests/${i}/count`;
|
|
8560
8560
|
const failuresField = `/tests/${i}/failures`;
|
|
8561
8561
|
if (test.record.kind !== "junit") {
|
|
8562
|
-
|
|
8562
|
+
const reason = test.record.kind === "local" ? "local_run_not_in_record" : "no_comparable_record_field";
|
|
8563
|
+
rows.push(unverifiable("count", countField, test.count, reason));
|
|
8563
8564
|
if (test.failures !== void 0) {
|
|
8564
|
-
rows.push(
|
|
8565
|
-
unverifiable("count", failuresField, test.failures, "no_comparable_record_field")
|
|
8566
|
-
);
|
|
8565
|
+
rows.push(unverifiable("count", failuresField, test.failures, reason));
|
|
8567
8566
|
}
|
|
8568
8567
|
return;
|
|
8569
8568
|
}
|
|
@@ -8617,7 +8616,7 @@ function countedCheckRuns(runs, excludedIds) {
|
|
|
8617
8616
|
return [...latest.values()].filter((run) => !excluded.has(run.id) && !superseded(run)).sort((a, b) => a.id - b.id);
|
|
8618
8617
|
}
|
|
8619
8618
|
var NOT_FAILED_CONCLUSIONS = ["success", "skipped", "neutral"];
|
|
8620
|
-
function booleanRow(field, declared, reason, runs, failed, mismatch,
|
|
8619
|
+
function booleanRow(field, declared, reason, runs, failed, mismatch, unsettled2) {
|
|
8621
8620
|
if (reason !== void 0) return unverifiable("count", field, declared, reason);
|
|
8622
8621
|
if (runs.length === 0) return unverifiable("count", field, declared, "no_check_runs");
|
|
8623
8622
|
let observed;
|
|
@@ -8625,7 +8624,7 @@ function booleanRow(field, declared, reason, runs, failed, mismatch, unsettled)
|
|
|
8625
8624
|
else if (runs.some((r) => r.status !== "completed")) {
|
|
8626
8625
|
return unverifiable("count", field, declared, "checks_incomplete");
|
|
8627
8626
|
} else {
|
|
8628
|
-
const why =
|
|
8627
|
+
const why = unsettled2?.(runs);
|
|
8629
8628
|
if (why !== void 0) return unverifiable("count", field, declared, why);
|
|
8630
8629
|
observed = true;
|
|
8631
8630
|
}
|
|
@@ -8688,8 +8687,8 @@ function checkMergedAt(block, evidence) {
|
|
|
8688
8687
|
}
|
|
8689
8688
|
return toSecond(declared) < toSecond(pr.mergedAt) ? [fail("time", "/mergedAt", declared, pr.mergedAt, "premature")] : [pass("time", "/mergedAt", declared, pr.mergedAt)];
|
|
8690
8689
|
}
|
|
8691
|
-
function earliestSuccess(section, relations,
|
|
8692
|
-
const times = section.deployments.filter((d) => relations.includes(d.relation) && d.successAt !== null).map((d) => d.successAt).filter((t) => toSecond(t) <= toSecond(
|
|
8690
|
+
function earliestSuccess(section, relations, at2) {
|
|
8691
|
+
const times = section.deployments.filter((d) => relations.includes(d.relation) && d.successAt !== null).map((d) => d.successAt).filter((t) => toSecond(t) <= toSecond(at2)).sort((a, b) => compareCodeUnits(toSecond(a), toSecond(b)) || compareCodeUnits(a, b));
|
|
8693
8692
|
return times[0];
|
|
8694
8693
|
}
|
|
8695
8694
|
function checkDeployedAt(block, evidence) {
|
|
@@ -8731,21 +8730,27 @@ var JunitError = class extends Error {
|
|
|
8731
8730
|
this.name = "JunitError";
|
|
8732
8731
|
}
|
|
8733
8732
|
};
|
|
8733
|
+
function countJunit(xml) {
|
|
8734
|
+
const { executed, failed } = scanJunit(xml);
|
|
8735
|
+
return { executed, failed };
|
|
8736
|
+
}
|
|
8734
8737
|
var NAME = /[A-Za-z_:][-A-Za-z0-9_:.]*/y;
|
|
8735
8738
|
var ENTITY = /&(?:lt|gt|amp|quot|apos|#[0-9]+|#x[0-9A-Fa-f]+);/y;
|
|
8736
|
-
function
|
|
8739
|
+
function scanJunit(xml) {
|
|
8737
8740
|
let pos = xml.startsWith("\uFEFF") ? 1 : 0;
|
|
8738
8741
|
const stack = [];
|
|
8739
8742
|
let roots = 0;
|
|
8740
8743
|
let executed = 0;
|
|
8741
8744
|
let failed = 0;
|
|
8745
|
+
let tagStart = 0;
|
|
8746
|
+
let root;
|
|
8742
8747
|
const fail2 = (message) => {
|
|
8743
8748
|
throw new JunitError(`${message} at offset ${pos}`);
|
|
8744
8749
|
};
|
|
8745
|
-
const checkText = (
|
|
8746
|
-
for (let i =
|
|
8750
|
+
const checkText = (text2, start) => {
|
|
8751
|
+
for (let i = text2.indexOf("&"); i !== -1; i = text2.indexOf("&", i + 1)) {
|
|
8747
8752
|
ENTITY.lastIndex = i;
|
|
8748
|
-
if (!ENTITY.test(
|
|
8753
|
+
if (!ENTITY.test(text2)) {
|
|
8749
8754
|
pos = start + i;
|
|
8750
8755
|
fail2("bad entity reference");
|
|
8751
8756
|
}
|
|
@@ -8766,6 +8771,13 @@ function countJunit(xml) {
|
|
|
8766
8771
|
roots++;
|
|
8767
8772
|
if (roots > 1) fail2("more than one root element");
|
|
8768
8773
|
if (name !== "testsuites" && name !== "testsuite") fail2(`root element is <${name}>`);
|
|
8774
|
+
root = {
|
|
8775
|
+
name,
|
|
8776
|
+
start: tagStart,
|
|
8777
|
+
end: pos,
|
|
8778
|
+
contentStart: pos,
|
|
8779
|
+
contentEnd: pos
|
|
8780
|
+
};
|
|
8769
8781
|
}
|
|
8770
8782
|
const parent = stack.at(-1);
|
|
8771
8783
|
if (parent?.testcase !== void 0) {
|
|
@@ -8784,16 +8796,21 @@ function countJunit(xml) {
|
|
|
8784
8796
|
executed++;
|
|
8785
8797
|
if (testcase.failed) failed++;
|
|
8786
8798
|
}
|
|
8799
|
+
if (stack.length === 0 && root !== void 0 && root.end < pos) {
|
|
8800
|
+
root.contentEnd = tagStart;
|
|
8801
|
+
root.end = pos;
|
|
8802
|
+
}
|
|
8787
8803
|
};
|
|
8788
8804
|
while (pos < xml.length) {
|
|
8789
8805
|
const lt = xml.indexOf("<", pos);
|
|
8790
8806
|
const textEnd = lt === -1 ? xml.length : lt;
|
|
8791
|
-
const
|
|
8792
|
-
if (stack.length === 0 &&
|
|
8793
|
-
checkText(
|
|
8794
|
-
if (
|
|
8807
|
+
const text2 = xml.slice(pos, textEnd);
|
|
8808
|
+
if (stack.length === 0 && text2.trim() !== "") fail2("text outside the root element");
|
|
8809
|
+
checkText(text2, pos);
|
|
8810
|
+
if (text2.includes("]]>")) fail2("']]>' in text");
|
|
8795
8811
|
pos = textEnd;
|
|
8796
8812
|
if (lt === -1) break;
|
|
8813
|
+
tagStart = pos;
|
|
8797
8814
|
if (xml.startsWith("<!--", pos)) {
|
|
8798
8815
|
const end = xml.indexOf("-->", pos + 4);
|
|
8799
8816
|
if (end === -1) fail2("unterminated comment");
|
|
@@ -8854,8 +8871,8 @@ function countJunit(xml) {
|
|
|
8854
8871
|
}
|
|
8855
8872
|
}
|
|
8856
8873
|
if (stack.length > 0) fail2(`unclosed <${stack.at(-1)?.name}>`);
|
|
8857
|
-
if (roots === 0) fail2("no root element");
|
|
8858
|
-
return { executed, failed };
|
|
8874
|
+
if (roots === 0 || root === void 0) return fail2("no root element");
|
|
8875
|
+
return { executed, failed, root };
|
|
8859
8876
|
}
|
|
8860
8877
|
|
|
8861
8878
|
// src/evidence/zip.ts
|
|
@@ -8975,11 +8992,11 @@ function splitRepo(repository) {
|
|
|
8975
8992
|
return { owner, name };
|
|
8976
8993
|
}
|
|
8977
8994
|
function timestamp(value) {
|
|
8978
|
-
const
|
|
8979
|
-
if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?Z$/.test(
|
|
8995
|
+
const text2 = str(value);
|
|
8996
|
+
if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?Z$/.test(text2)) {
|
|
8980
8997
|
throw new ShapeError();
|
|
8981
8998
|
}
|
|
8982
|
-
return
|
|
8999
|
+
return text2;
|
|
8983
9000
|
}
|
|
8984
9001
|
function timestampOrNull(value) {
|
|
8985
9002
|
return value === null || value === void 0 ? null : timestamp(value);
|
|
@@ -9220,10 +9237,10 @@ async function readGraphqlPages(client, kind, locator, query, variables, budget,
|
|
|
9220
9237
|
let head;
|
|
9221
9238
|
let after = null;
|
|
9222
9239
|
for (let n = 1; n <= budget; n++) {
|
|
9223
|
-
const
|
|
9240
|
+
const at2 = `${locator}${after === null ? "" : `(after:${after})`}`;
|
|
9224
9241
|
const read = await client.read(kind, "/graphql", {
|
|
9225
9242
|
method: "POST",
|
|
9226
|
-
locator:
|
|
9243
|
+
locator: at2,
|
|
9227
9244
|
requestBody: JSON.stringify({ query, variables: { ...variables, after } })
|
|
9228
9245
|
});
|
|
9229
9246
|
if (!read.ok) return unreadable(kind);
|
|
@@ -9239,7 +9256,7 @@ async function readGraphqlPages(client, kind, locator, query, variables, budget,
|
|
|
9239
9256
|
after = str(info.endCursor);
|
|
9240
9257
|
} catch (e) {
|
|
9241
9258
|
if (!(e instanceof ShapeError)) throw e;
|
|
9242
|
-
client.markParseError(
|
|
9259
|
+
client.markParseError(at2);
|
|
9243
9260
|
return unreadable(kind);
|
|
9244
9261
|
}
|
|
9245
9262
|
}
|
|
@@ -9291,17 +9308,17 @@ var TARGET_STATES = {
|
|
|
9291
9308
|
};
|
|
9292
9309
|
function closeEvent(node2) {
|
|
9293
9310
|
const n = obj(node2);
|
|
9294
|
-
const
|
|
9295
|
-
if (n.closer === null) return { at, closer: null };
|
|
9311
|
+
const at2 = timestamp(n.createdAt);
|
|
9312
|
+
if (n.closer === null) return { at: at2, closer: null };
|
|
9296
9313
|
const closer = obj(n.closer);
|
|
9297
9314
|
const type = str(closer.__typename);
|
|
9298
9315
|
if (type === "PullRequest") {
|
|
9299
9316
|
const ref = `${str(obj(closer.repository).nameWithOwner)}#${int(closer.number)}`;
|
|
9300
9317
|
if (!ISSUE_REF.test(ref)) throw new ShapeError();
|
|
9301
|
-
return { at, closer: { kind: "pull_request", ref } };
|
|
9318
|
+
return { at: at2, closer: { kind: "pull_request", ref } };
|
|
9302
9319
|
}
|
|
9303
|
-
if (type === "Commit") return { at, closer: { kind: "commit", oid: commitOid(closer.oid) } };
|
|
9304
|
-
return { at, closer: { kind: "other" } };
|
|
9320
|
+
if (type === "Commit") return { at: at2, closer: { kind: "commit", oid: commitOid(closer.oid) } };
|
|
9321
|
+
return { at: at2, closer: { kind: "other" } };
|
|
9305
9322
|
}
|
|
9306
9323
|
async function readCloseTarget(client, ref) {
|
|
9307
9324
|
const [repo, num] = ref.split("#");
|
|
@@ -9342,9 +9359,9 @@ function closeTargets(block, repository, closing, references) {
|
|
|
9342
9359
|
if (listed2(target)) continue;
|
|
9343
9360
|
const entry = references?.find((r) => r.kind === "issue" && sameIssue(r.ref, target));
|
|
9344
9361
|
if (entry?.kind !== "issue" || entry.status !== "ok" || !entry.exists) continue;
|
|
9345
|
-
const
|
|
9346
|
-
if (listed2(
|
|
9347
|
-
out.push(
|
|
9362
|
+
const at2 = entry.resolvedAs ?? target;
|
|
9363
|
+
if (listed2(at2) || out.some((o) => sameIssue(o, at2))) continue;
|
|
9364
|
+
out.push(at2);
|
|
9348
9365
|
}
|
|
9349
9366
|
return out;
|
|
9350
9367
|
}
|
|
@@ -9584,9 +9601,9 @@ function withinLatestAttempt(jobs, run, createdAt) {
|
|
|
9584
9601
|
(job) => (job.attempt === null || job.attempt <= run.attempt) && job.startedAt !== null && job.completedAt !== null && second(job.startedAt) <= created && created <= second(job.completedAt)
|
|
9585
9602
|
);
|
|
9586
9603
|
}
|
|
9587
|
-
async function readTestRecord(client, repository, record2,
|
|
9604
|
+
async function readTestRecord(client, repository, record2, headCommit2, observed) {
|
|
9588
9605
|
const workflowFile = record2.workflow.split("/").at(-1);
|
|
9589
|
-
const runsPath = (page) => `/repos/${repository}/actions/workflows/${encodeURIComponent(workflowFile)}/runs?head_sha=${
|
|
9606
|
+
const runsPath = (page) => `/repos/${repository}/actions/workflows/${encodeURIComponent(workflowFile)}/runs?head_sha=${headCommit2}&per_page=${PER_PAGE}&page=${page}`;
|
|
9590
9607
|
const listed2 = await readAllPages(
|
|
9591
9608
|
client,
|
|
9592
9609
|
"workflow_runs",
|
|
@@ -9594,12 +9611,24 @@ async function readTestRecord(client, repository, record2, headCommit) {
|
|
|
9594
9611
|
(json2) => arr(obj(json2).workflow_runs),
|
|
9595
9612
|
(item) => {
|
|
9596
9613
|
const r = obj(item);
|
|
9597
|
-
return {
|
|
9614
|
+
return {
|
|
9615
|
+
id: int(r.id),
|
|
9616
|
+
attempt: int(r.run_attempt),
|
|
9617
|
+
path: str(r.path),
|
|
9618
|
+
// Read only for `observed`, and never a shape error: a status that is not a string is not
|
|
9619
|
+
// `completed`.
|
|
9620
|
+
completed: r.status === "completed"
|
|
9621
|
+
};
|
|
9598
9622
|
},
|
|
9599
9623
|
// No workflow with that file name: there is no run, so no candidate run.
|
|
9600
9624
|
{ notFoundIsEmpty: true }
|
|
9601
9625
|
);
|
|
9602
9626
|
if (listed2 === null) return { record: record2, ...unreadable("workflow_runs") };
|
|
9627
|
+
if (observed !== void 0) {
|
|
9628
|
+
observed.runsNotCompleted = listed2.filter(
|
|
9629
|
+
(r) => r.path.replace(/@.*$/, "") === record2.workflow && !r.completed
|
|
9630
|
+
).length;
|
|
9631
|
+
}
|
|
9603
9632
|
const runs = [];
|
|
9604
9633
|
for (const run of listed2.filter((r) => r.path.replace(/@.*$/, "") === record2.workflow)) {
|
|
9605
9634
|
const jobs = await readAllPages(
|
|
@@ -9691,7 +9720,7 @@ async function readDeployments(client, repository, pr, deployedAt) {
|
|
|
9691
9720
|
for (const d of ofHead) candidates.set(d.id, d);
|
|
9692
9721
|
if (pr.merged && pr.mergedAt !== null && pr.mergeSha !== null) {
|
|
9693
9722
|
const mergedAt = second(pr.mergedAt);
|
|
9694
|
-
const
|
|
9723
|
+
const at2 = second(deployedAt.at);
|
|
9695
9724
|
const window = await readAllPages(
|
|
9696
9725
|
client,
|
|
9697
9726
|
"deployments",
|
|
@@ -9703,7 +9732,7 @@ async function readDeployments(client, repository, pr, deployedAt) {
|
|
|
9703
9732
|
if (window === null) return fail2("deployments");
|
|
9704
9733
|
for (const d of window) {
|
|
9705
9734
|
const created = second(d.createdAt);
|
|
9706
|
-
if (created >= mergedAt && created <=
|
|
9735
|
+
if (created >= mergedAt && created <= at2) candidates.set(d.id, d);
|
|
9707
9736
|
}
|
|
9708
9737
|
}
|
|
9709
9738
|
const deployments = [];
|
|
@@ -9852,7 +9881,7 @@ async function reportFromComment(client, repository, number4, login) {
|
|
|
9852
9881
|
// src/record/checker.ts
|
|
9853
9882
|
import { readFileSync } from "node:fs";
|
|
9854
9883
|
var CHECKER_NAME = "dunstan";
|
|
9855
|
-
var CHECKER_VERSION = "0.1.
|
|
9884
|
+
var CHECKER_VERSION = "0.1.14";
|
|
9856
9885
|
function checkerIdentity(artifact) {
|
|
9857
9886
|
return {
|
|
9858
9887
|
name: CHECKER_NAME,
|
|
@@ -9962,16 +9991,35 @@ var FILES = { "handback-block-0.1.schema.json": `{
|
|
|
9962
9991
|
}
|
|
9963
9992
|
},
|
|
9964
9993
|
"testRecord": {
|
|
9965
|
-
"description": "Names the machine-readable test record a count is compared with. v0.1 defines the junit kind. Any other kind is legal and yields an unverifiable claim.",
|
|
9994
|
+
"description": "Names the machine-readable test record a count is compared with. v0.1 defines the junit kind and, since spec 0.1.9, the local kind. Any other kind is legal and yields an unverifiable claim.",
|
|
9966
9995
|
"type": "object",
|
|
9967
9996
|
"required": ["kind"],
|
|
9968
9997
|
"properties": {
|
|
9969
9998
|
"kind": { "type": "string", "minLength": 1, "maxLength": 100 }
|
|
9970
9999
|
},
|
|
9971
|
-
"
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
10000
|
+
"allOf": [
|
|
10001
|
+
{
|
|
10002
|
+
"if": {
|
|
10003
|
+
"properties": { "kind": { "const": "junit" } }
|
|
10004
|
+
},
|
|
10005
|
+
"then": { "$ref": "#/$defs/junitRecord" }
|
|
10006
|
+
},
|
|
10007
|
+
{
|
|
10008
|
+
"if": {
|
|
10009
|
+
"properties": { "kind": { "const": "local" } }
|
|
10010
|
+
},
|
|
10011
|
+
"then": { "$ref": "#/$defs/localRecord" }
|
|
10012
|
+
}
|
|
10013
|
+
]
|
|
10014
|
+
},
|
|
10015
|
+
"localRecord": {
|
|
10016
|
+
"description": "Counts from a run that left no record in CI, declared as such (spec 0.1.9). Exactly the kind; its claims are unverifiable with local_run_not_in_record.",
|
|
10017
|
+
"type": "object",
|
|
10018
|
+
"required": ["kind"],
|
|
10019
|
+
"additionalProperties": false,
|
|
10020
|
+
"properties": {
|
|
10021
|
+
"kind": { "const": "local" }
|
|
10022
|
+
}
|
|
9975
10023
|
},
|
|
9976
10024
|
"junitRecord": {
|
|
9977
10025
|
"type": "object",
|
|
@@ -10029,7 +10077,7 @@ var FILES = { "handback-block-0.1.schema.json": `{
|
|
|
10029
10077
|
}
|
|
10030
10078
|
}
|
|
10031
10079
|
}
|
|
10032
|
-
`, "record-0.1.schema.json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://barglabs.ai/dunstan/spec/schema/record-0.1.schema.json",\n "title": "Dunstan record, v0.1",\n "description": "An in-toto Statement v1 whose predicate records one check of a handback block against the record. See spec/claim-format.md.",\n "type": "object",\n "required": ["_type", "subject", "predicateType", "predicate"],\n "additionalProperties": false,\n "properties": {\n "_type": { "const": "https://in-toto.io/Statement/v1" },\n "subject": {\n "type": "array",\n "minItems": 1,\n "maxItems": 2,\n "prefixItems": [{ "$ref": "#/$defs/commitSubject" }, { "$ref": "#/$defs/blockSubject" }],\n "items": false\n },\n "predicateType": { "const": "https://barglabs.ai/dunstan/record/v0.1" },\n "predicate": { "$ref": "#/$defs/predicate" }\n },\n "allOf": [\n {\n "if": {\n "properties": {\n "predicate": {\n "properties": { "block": { "properties": { "status": { "const": "found" } } } }\n }\n }\n },\n "then": { "properties": { "subject": { "minItems": 2 } } },\n "else": { "properties": { "subject": { "maxItems": 1 } } }\n }\n ],\n "$defs": {\n "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },\n "commitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" },\n "repository": {\n "description": "owner/repo",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}$"\n },\n "issueRef": {\n "description": "owner/repo#N, always fully qualified in evidence.",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,9}$"\n },\n "timestamp": {\n "type": "string",\n "format": "date-time",\n "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\\\.[0-9]+)?Z$"\n },\n "sourceKind": {\n "enum": [\n "pull_request",\n "pull_request_files",\n "pull_request_commits",\n "closing_references",\n "repository",\n "issue",\n "commit",\n "compare",\n "check_runs",\n "workflow_runs",\n "artifact",\n "deployments"\n ]\n },\n "commitSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string",\n "pattern": "^git\\\\+https://github\\\\.com/[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}@[0-9a-f]{40}$"\n },\n "digest": {\n "type": "object",\n "required": ["gitCommit"],\n "additionalProperties": false,\n "properties": { "gitCommit": { "$ref": "#/$defs/commitSha" } }\n }\n }\n },\n "blockSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "const": "handback-block" },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "predicate": {\n "type": "object",\n "required": [\n "spec",\n "checker",\n "report",\n "block",\n "subject",\n "evidence",\n "claims",\n "verdict",\n "digests",\n "rerun",\n "assurance"\n ],\n "additionalProperties": false,\n "properties": {\n "spec": {\n "description": "The spec version the checker implements, semver.",\n "type": "string",\n "pattern": "^0\\\\.1\\\\.(?:0|[1-9][0-9]*)$"\n },\n "checker": {\n "type": "object",\n "required": ["name", "version", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "type": "string", "minLength": 1, "maxLength": 214 },\n "version": { "type": "string", "minLength": 1, "maxLength": 100 },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "report": {\n "description": "The report\'s digest and where it was read. Never its text.",\n "type": "object",\n "required": ["sha256", "source"],\n "additionalProperties": false,\n "properties": {\n "sha256": { "$ref": "#/$defs/sha256" },\n "source": {\n "type": "object",\n "required": ["kind", "locator"],\n "additionalProperties": false,\n "properties": {\n "kind": { "enum": ["pr-body", "pr-comment", "file", "stdin", "api"] },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n }\n }\n },\n "block": { "$ref": "#/$defs/block" },\n "subject": {\n "type": "object",\n "required": ["repository", "pullRequest", "headSha", "mergeSha"],\n "additionalProperties": false,\n "properties": {\n "repository": { "$ref": "#/$defs/repository" },\n "pullRequest": { "type": "integer", "minimum": 1 },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": {\n "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }]\n }\n }\n },\n "evidence": { "$ref": "#/$defs/evidence" },\n "claims": {\n "type": "array",\n "items": { "$ref": "#/$defs/claim" }\n },\n "verdict": { "$ref": "#/$defs/verdict" },\n "digests": {\n "type": "object",\n "required": ["claims", "evidence"],\n "additionalProperties": false,\n "properties": {\n "claims": { "$ref": "#/$defs/sha256" },\n "evidence": { "$ref": "#/$defs/sha256" }\n }\n },\n "rerun": {\n "type": "object",\n "required": ["offline", "online"],\n "additionalProperties": false,\n "properties": {\n "offline": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "online": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n },\n "assurance": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issuer"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "unsigned" },\n "issuer": { "const": "self-generated" }\n }\n },\n {\n "type": "object",\n "required": ["status", "issuer", "keyFingerprint"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "signed" },\n "issuer": { "type": "string", "minLength": 1, "maxLength": 320 },\n "keyFingerprint": { "type": "string", "pattern": "^SHA256:[A-Za-z0-9+/]{43}$" }\n }\n }\n ]\n }\n }\n },\n "verdict": { "enum": ["pass", "fail", "unverifiable"] },\n "blockError": {\n "type": "object",\n "required": ["code"],\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "unterminated_fence",\n "invalid_json",\n "duplicate_member",\n "unsupported_version",\n "schema_violation"\n ]\n },\n "pointer": { "type": "string", "pattern": "^(?:/.*)?$" },\n "keyword": { "type": "string", "minLength": 1 }\n }\n },\n "block": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "found" },\n "sha256": { "$ref": "#/$defs/sha256" },\n "value": { "$ref": "handback-block-0.1.schema.json" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "missing" },\n "reason": { "const": "block_missing" },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "count", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ambiguous" },\n "reason": { "const": "block_ambiguous" },\n "count": { "type": "integer", "minimum": 2 },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "errors", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "invalid" },\n "reason": { "const": "block_invalid" },\n "errors": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/blockError" } },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n }\n ]\n },\n "claim": {\n "type": "object",\n "required": ["id", "check", "field", "declared", "observed", "verdict"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "string", "minLength": 1 },\n "check": { "enum": ["head", "scope", "reference", "count", "time"] },\n "field": {\n "description": "A JSON Pointer (RFC 6901) into the block.",\n "type": "string",\n "pattern": "^/.+$"\n },\n "declared": true,\n "observed": true,\n "verdict": { "$ref": "#/$defs/verdict" },\n "reason": { "$ref": "#/$defs/claimReason" }\n },\n "if": { "properties": { "verdict": { "const": "pass" } } },\n "then": { "not": { "required": ["reason"] } },\n "else": { "required": ["reason"] }\n },\n "claimReason": {\n "type": "string",\n "pattern": "^(?:head_mismatch|declared_not_changed|undeclared_file|file_list_truncated|not_closing|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|not_found|not_reachable|count_mismatch|all_succeeded_mismatch|none_failed_mismatch|checks_incomplete|no_check_runs|no_succeeded_check_runs|check_runs_not_at_head|no_comparable_record_field|record_not_found|record_ambiguous|not_merged|premature|no_deployment|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request|pull_request_files|pull_request_commits|closing_references|repository|issue|commit|compare|check_runs|workflow_runs|artifact|deployments))$"\n },\n "unread": {\n "description": "A section the checker could not use: its source errored, or the source answered without the field.",\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "source"],\n "properties": {\n "status": { "const": "unreadable" },\n "source": { "$ref": "#/$defs/sourceKind" }\n }\n },\n {\n "type": "object",\n "required": ["status", "field"],\n "properties": {\n "status": { "const": "unpopulated" },\n "field": { "type": "string", "pattern": "^[A-Za-z0-9_.]+$" }\n }\n }\n ]\n },\n "evidence": {\n "type": "object",\n "required": ["pullRequest", "sources"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "description": "Always read: a checker that cannot read the pull request writes no record.",\n "type": "object",\n "required": [\n "status",\n "number",\n "state",\n "merged",\n "mergedAt",\n "headSha",\n "mergeSha",\n "changedFiles"\n ],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "number": { "type": "integer", "minimum": 1 },\n "state": { "enum": ["open", "closed"] },\n "merged": { "type": "boolean" },\n "mergedAt": { "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }] },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": { "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }] },\n "changedFiles": { "type": "integer", "minimum": 0 }\n }\n },\n "files": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "complete", "entries"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "complete": { "type": "boolean" },\n "entries": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["path", "status"],\n "additionalProperties": false,\n "properties": {\n "path": { "type": "string", "minLength": 1 },\n "status": {\n "enum": [\n "added",\n "removed",\n "modified",\n "renamed",\n "copied",\n "changed",\n "unchanged"\n ]\n },\n "previousPath": { "type": "string", "minLength": 1 }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "closingReferences": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issues"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "issues": { "type": "array", "items": { "$ref": "#/$defs/issueRef" } }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "references": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "issue" },\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "resolvedAs": {\n "description": "Spec 0.1.3: the owner/repo#N GitHub answered the read of ref with, when it is another issue (an issue transferred to another repository).",\n "$ref": "#/$defs/issueRef"\n }\n },\n "if": { "required": ["resolvedAs"] },\n "then": { "properties": { "exists": { "const": true } } }\n },\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists", "reachableFromHead"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "ref": { "$ref": "#/$defs/commitSha" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "reachableFromHead": { "type": "boolean" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "properties": {\n "kind": { "enum": ["issue", "commit"] },\n "ref": { "type": "string", "minLength": 1 }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "closeEvents": {\n "description": "Spec 0.1.4: who closed each closes target that exists and is not among the closing references, read under the identity it resolves to; and the pull request\'s base, its repository\'s default branch and its commit ids.",\n "type": "object",\n "required": ["pullRequest", "targets"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "baseRef", "defaultBranch", "commits"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "baseRef": { "type": "string", "minLength": 1 },\n "defaultBranch": {\n "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }]\n },\n "commits": {\n "type": "array",\n "uniqueItems": true,\n "items": { "$ref": "#/$defs/commitSha" }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "targets": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["ref", "status", "type", "state", "events"],\n "additionalProperties": false,\n "properties": {\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "type": { "enum": ["issue", "pull_request"] },\n "state": { "enum": ["open", "closed", "merged"] },\n "events": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["at", "closer"],\n "additionalProperties": false,\n "properties": {\n "at": { "$ref": "#/$defs/timestamp" },\n "closer": {\n "oneOf": [\n { "type": "null" },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "pull_request" },\n "ref": { "$ref": "#/$defs/issueRef" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "oid"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "oid": { "$ref": "#/$defs/commitSha" }\n }\n },\n {\n "type": "object",\n "required": ["kind"],\n "additionalProperties": false,\n "properties": { "kind": { "const": "other" } }\n }\n ]\n }\n }\n }\n }\n },\n "if": { "properties": { "type": { "const": "issue" } } },\n "then": { "properties": { "state": { "enum": ["open", "closed"] } } }\n },\n {\n "type": "object",\n "required": ["ref"],\n "properties": { "ref": { "$ref": "#/$defs/issueRef" } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n }\n }\n },\n "checkRuns": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "commit", "excludedIds", "runs"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "commit": { "$ref": "#/$defs/commitSha" },\n "excludedIds": {\n "description": "Check runs left out of the count: the run the checker itself executes in.",\n "type": "array",\n "items": { "type": "integer", "minimum": 1 }\n },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "name", "status", "conclusion"],\n "additionalProperties": false,\n "dependentRequired": {\n "workflowPath": ["checkSuiteId", "workflowRunId", "runAttempt"],\n "workflowName": ["workflowPath"],\n "workflowRunId": ["workflowPath"],\n "runAttempt": ["workflowPath"]\n },\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "name": { "type": "string" },\n "status": { "type": "string", "minLength": 1 },\n "conclusion": { "type": ["string", "null"] },\n "checkSuiteId": {\n "description": "The run\'s check suite (`check_suite.id`). A run without `workflowPath` is counted as the latest per check suite and name; a run without either is keyed by its name alone. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "integer",\n "minimum": 1\n },\n "appSlug": {\n "description": "The slug of the app that created the run (`app.slug`). Recorded, not read by any check. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "string",\n "minLength": 1\n },\n "workflowPath": {\n "description": "The `path` of the workflow run whose `check_suite_id` is the run\'s check suite, less any `@<ref>`, from the workflow runs at the head. Runs that have it are counted as the latest per workflow path and name. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "string",\n "minLength": 1\n },\n "workflowName": {\n "description": "The `name` of that workflow run, as the listing gives it; absent where the listing gives none, null or the empty string. No 0.1 check reads it. Added in spec 0.1.5.",\n "type": "string",\n "minLength": 1\n },\n "workflowRunId": {\n "description": "The `id` of that workflow run. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n },\n "runAttempt": {\n "description": "That workflow run\'s `run_attempt` as the listing gives it, its latest attempt at the read. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "testRecords": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["record", "status", "runs"],\n "additionalProperties": false,\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" },\n "status": { "const": "ok" },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["runId", "runAttempt", "executed", "failed"],\n "additionalProperties": false,\n "properties": {\n "runId": { "type": "integer", "minimum": 1 },\n "runAttempt": { "type": "integer", "minimum": 1 },\n "executed": { "type": "integer", "minimum": 0 },\n "failed": { "type": "integer", "minimum": 0 }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["record"],\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "deployments": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["environment", "status", "deployments"],\n "additionalProperties": false,\n "properties": {\n "environment": { "type": "string", "minLength": 1 },\n "status": { "const": "ok" },\n "deployments": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "sha", "relation", "successAt"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "sha": { "$ref": "#/$defs/commitSha" },\n "relation": {\n "enum": ["head", "merge", "descendant", "unrelated", "unknown"]\n },\n "successAt": {\n "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }]\n }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["environment"],\n "properties": { "environment": { "type": "string", "minLength": 1 } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "sources": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["kind", "locator", "sha256", "readAt"],\n "additionalProperties": false,\n "properties": {\n "kind": { "$ref": "#/$defs/sourceKind" },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "sha256": { "$ref": "#/$defs/sha256" },\n "etag": { "type": "string", "minLength": 1 },\n "readAt": { "$ref": "#/$defs/timestamp" },\n "error": {\n "description": "Present when the read failed: http_<status>, network, or parse.",\n "type": "string",\n "pattern": "^(?:http_[1-5][0-9]{2}|network|parse)$"\n }\n }\n }\n }\n }\n },\n "unreadOnly": {\n "allOf": [{ "$ref": "#/$defs/unread" }],\n "unevaluatedProperties": false\n }\n }\n}\n', "record-0.2-draft.schema.json": `{
|
|
10080
|
+
`, "record-0.1.schema.json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://barglabs.ai/dunstan/spec/schema/record-0.1.schema.json",\n "title": "Dunstan record, v0.1",\n "description": "An in-toto Statement v1 whose predicate records one check of a handback block against the record. See spec/claim-format.md.",\n "type": "object",\n "required": ["_type", "subject", "predicateType", "predicate"],\n "additionalProperties": false,\n "properties": {\n "_type": { "const": "https://in-toto.io/Statement/v1" },\n "subject": {\n "type": "array",\n "minItems": 1,\n "maxItems": 2,\n "prefixItems": [{ "$ref": "#/$defs/commitSubject" }, { "$ref": "#/$defs/blockSubject" }],\n "items": false\n },\n "predicateType": { "const": "https://barglabs.ai/dunstan/record/v0.1" },\n "predicate": { "$ref": "#/$defs/predicate" }\n },\n "allOf": [\n {\n "if": {\n "properties": {\n "predicate": {\n "properties": { "block": { "properties": { "status": { "const": "found" } } } }\n }\n }\n },\n "then": { "properties": { "subject": { "minItems": 2 } } },\n "else": { "properties": { "subject": { "maxItems": 1 } } }\n }\n ],\n "$defs": {\n "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },\n "commitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" },\n "repository": {\n "description": "owner/repo",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}$"\n },\n "issueRef": {\n "description": "owner/repo#N, always fully qualified in evidence.",\n "type": "string",\n "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,9}$"\n },\n "timestamp": {\n "type": "string",\n "format": "date-time",\n "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\\\.[0-9]+)?Z$"\n },\n "sourceKind": {\n "enum": [\n "pull_request",\n "pull_request_files",\n "pull_request_commits",\n "closing_references",\n "repository",\n "issue",\n "commit",\n "compare",\n "check_runs",\n "workflow_runs",\n "artifact",\n "deployments"\n ]\n },\n "commitSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": {\n "type": "string",\n "pattern": "^git\\\\+https://github\\\\.com/[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}@[0-9a-f]{40}$"\n },\n "digest": {\n "type": "object",\n "required": ["gitCommit"],\n "additionalProperties": false,\n "properties": { "gitCommit": { "$ref": "#/$defs/commitSha" } }\n }\n }\n },\n "blockSubject": {\n "type": "object",\n "required": ["name", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "const": "handback-block" },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "predicate": {\n "type": "object",\n "required": [\n "spec",\n "checker",\n "report",\n "block",\n "subject",\n "evidence",\n "claims",\n "verdict",\n "digests",\n "rerun",\n "assurance"\n ],\n "additionalProperties": false,\n "properties": {\n "spec": {\n "description": "The spec version the checker implements, semver.",\n "type": "string",\n "pattern": "^0\\\\.1\\\\.(?:0|[1-9][0-9]*)$"\n },\n "checker": {\n "type": "object",\n "required": ["name", "version", "digest"],\n "additionalProperties": false,\n "properties": {\n "name": { "type": "string", "minLength": 1, "maxLength": 214 },\n "version": { "type": "string", "minLength": 1, "maxLength": 100 },\n "digest": {\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "#/$defs/sha256" } }\n }\n }\n },\n "report": {\n "description": "The report\'s digest and where it was read. Never its text.",\n "type": "object",\n "required": ["sha256", "source"],\n "additionalProperties": false,\n "properties": {\n "sha256": { "$ref": "#/$defs/sha256" },\n "source": {\n "type": "object",\n "required": ["kind", "locator"],\n "additionalProperties": false,\n "properties": {\n "kind": { "enum": ["pr-body", "pr-comment", "file", "stdin", "api"] },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n }\n }\n },\n "block": { "$ref": "#/$defs/block" },\n "subject": {\n "type": "object",\n "required": ["repository", "pullRequest", "headSha", "mergeSha"],\n "additionalProperties": false,\n "properties": {\n "repository": { "$ref": "#/$defs/repository" },\n "pullRequest": { "type": "integer", "minimum": 1 },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": {\n "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }]\n }\n }\n },\n "evidence": { "$ref": "#/$defs/evidence" },\n "claims": {\n "type": "array",\n "items": { "$ref": "#/$defs/claim" }\n },\n "verdict": { "$ref": "#/$defs/verdict" },\n "digests": {\n "type": "object",\n "required": ["claims", "evidence"],\n "additionalProperties": false,\n "properties": {\n "claims": { "$ref": "#/$defs/sha256" },\n "evidence": { "$ref": "#/$defs/sha256" }\n }\n },\n "rerun": {\n "type": "object",\n "required": ["offline", "online"],\n "additionalProperties": false,\n "properties": {\n "offline": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "online": { "type": "string", "minLength": 1, "maxLength": 2000 }\n }\n },\n "assurance": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issuer"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "unsigned" },\n "issuer": { "const": "self-generated" }\n }\n },\n {\n "type": "object",\n "required": ["status", "issuer", "keyFingerprint"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "signed" },\n "issuer": { "type": "string", "minLength": 1, "maxLength": 320 },\n "keyFingerprint": { "type": "string", "pattern": "^SHA256:[A-Za-z0-9+/]{43}$" }\n }\n }\n ]\n }\n }\n },\n "verdict": { "enum": ["pass", "fail", "unverifiable"] },\n "blockError": {\n "type": "object",\n "required": ["code"],\n "additionalProperties": false,\n "properties": {\n "code": {\n "enum": [\n "unterminated_fence",\n "invalid_json",\n "duplicate_member",\n "unsupported_version",\n "schema_violation"\n ]\n },\n "pointer": { "type": "string", "pattern": "^(?:/.*)?$" },\n "keyword": { "type": "string", "minLength": 1 }\n }\n },\n "block": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "found" },\n "sha256": { "$ref": "#/$defs/sha256" },\n "value": { "$ref": "handback-block-0.1.schema.json" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "missing" },\n "reason": { "const": "block_missing" },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "count", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ambiguous" },\n "reason": { "const": "block_ambiguous" },\n "count": { "type": "integer", "minimum": 2 },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n },\n {\n "type": "object",\n "required": ["status", "reason", "errors", "sha256", "value"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "invalid" },\n "reason": { "const": "block_invalid" },\n "errors": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/blockError" } },\n "sha256": { "type": "null" },\n "value": { "type": "null" }\n }\n }\n ]\n },\n "claim": {\n "type": "object",\n "required": ["id", "check", "field", "declared", "observed", "verdict"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "string", "minLength": 1 },\n "check": { "enum": ["head", "scope", "reference", "count", "time"] },\n "field": {\n "description": "A JSON Pointer (RFC 6901) into the block.",\n "type": "string",\n "pattern": "^/.+$"\n },\n "declared": true,\n "observed": true,\n "verdict": { "$ref": "#/$defs/verdict" },\n "reason": { "$ref": "#/$defs/claimReason" }\n },\n "if": { "properties": { "verdict": { "const": "pass" } } },\n "then": { "not": { "required": ["reason"] } },\n "else": { "required": ["reason"] }\n },\n "claimReason": {\n "type": "string",\n "pattern": "^(?:head_mismatch|declared_not_changed|undeclared_file|file_list_truncated|not_closing|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|not_found|not_reachable|count_mismatch|all_succeeded_mismatch|none_failed_mismatch|checks_incomplete|no_check_runs|no_succeeded_check_runs|check_runs_not_at_head|no_comparable_record_field|local_run_not_in_record|record_not_found|record_ambiguous|not_merged|premature|no_deployment|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request|pull_request_files|pull_request_commits|closing_references|repository|issue|commit|compare|check_runs|workflow_runs|artifact|deployments))$"\n },\n "unread": {\n "description": "A section the checker could not use: its source errored, or the source answered without the field.",\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "source"],\n "properties": {\n "status": { "const": "unreadable" },\n "source": { "$ref": "#/$defs/sourceKind" }\n }\n },\n {\n "type": "object",\n "required": ["status", "field"],\n "properties": {\n "status": { "const": "unpopulated" },\n "field": { "type": "string", "pattern": "^[A-Za-z0-9_.]+$" }\n }\n }\n ]\n },\n "evidence": {\n "type": "object",\n "required": ["pullRequest", "sources"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "description": "Always read: a checker that cannot read the pull request writes no record.",\n "type": "object",\n "required": [\n "status",\n "number",\n "state",\n "merged",\n "mergedAt",\n "headSha",\n "mergeSha",\n "changedFiles"\n ],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "number": { "type": "integer", "minimum": 1 },\n "state": { "enum": ["open", "closed"] },\n "merged": { "type": "boolean" },\n "mergedAt": { "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }] },\n "headSha": { "$ref": "#/$defs/commitSha" },\n "mergeSha": { "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }] },\n "changedFiles": { "type": "integer", "minimum": 0 }\n }\n },\n "files": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "complete", "entries"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "complete": { "type": "boolean" },\n "entries": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["path", "status"],\n "additionalProperties": false,\n "properties": {\n "path": { "type": "string", "minLength": 1 },\n "status": {\n "enum": [\n "added",\n "removed",\n "modified",\n "renamed",\n "copied",\n "changed",\n "unchanged"\n ]\n },\n "previousPath": { "type": "string", "minLength": 1 }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "closingReferences": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "issues"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "issues": { "type": "array", "items": { "$ref": "#/$defs/issueRef" } }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "references": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "issue" },\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "resolvedAs": {\n "description": "Spec 0.1.3: the owner/repo#N GitHub answered the read of ref with, when it is another issue (an issue transferred to another repository).",\n "$ref": "#/$defs/issueRef"\n }\n },\n "if": { "required": ["resolvedAs"] },\n "then": { "properties": { "exists": { "const": true } } }\n },\n {\n "type": "object",\n "required": ["kind", "ref", "status", "exists", "reachableFromHead"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "ref": { "$ref": "#/$defs/commitSha" },\n "status": { "const": "ok" },\n "exists": { "type": "boolean" },\n "reachableFromHead": { "type": "boolean" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "properties": {\n "kind": { "enum": ["issue", "commit"] },\n "ref": { "type": "string", "minLength": 1 }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "closeEvents": {\n "description": "Spec 0.1.4: who closed each closes target that exists and is not among the closing references, read under the identity it resolves to; and the pull request\'s base, its repository\'s default branch and its commit ids.",\n "type": "object",\n "required": ["pullRequest", "targets"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "baseRef", "defaultBranch", "commits"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "baseRef": { "type": "string", "minLength": 1 },\n "defaultBranch": {\n "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }]\n },\n "commits": {\n "type": "array",\n "uniqueItems": true,\n "items": { "$ref": "#/$defs/commitSha" }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "targets": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["ref", "status", "type", "state", "events"],\n "additionalProperties": false,\n "properties": {\n "ref": { "$ref": "#/$defs/issueRef" },\n "status": { "const": "ok" },\n "type": { "enum": ["issue", "pull_request"] },\n "state": { "enum": ["open", "closed", "merged"] },\n "events": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["at", "closer"],\n "additionalProperties": false,\n "properties": {\n "at": { "$ref": "#/$defs/timestamp" },\n "closer": {\n "oneOf": [\n { "type": "null" },\n {\n "type": "object",\n "required": ["kind", "ref"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "pull_request" },\n "ref": { "$ref": "#/$defs/issueRef" }\n }\n },\n {\n "type": "object",\n "required": ["kind", "oid"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "commit" },\n "oid": { "$ref": "#/$defs/commitSha" }\n }\n },\n {\n "type": "object",\n "required": ["kind"],\n "additionalProperties": false,\n "properties": { "kind": { "const": "other" } }\n }\n ]\n }\n }\n }\n }\n },\n "if": { "properties": { "type": { "const": "issue" } } },\n "then": { "properties": { "state": { "enum": ["open", "closed"] } } }\n },\n {\n "type": "object",\n "required": ["ref"],\n "properties": { "ref": { "$ref": "#/$defs/issueRef" } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n }\n }\n },\n "checkRuns": {\n "oneOf": [\n {\n "type": "object",\n "required": ["status", "commit", "excludedIds", "runs"],\n "additionalProperties": false,\n "properties": {\n "status": { "const": "ok" },\n "commit": { "$ref": "#/$defs/commitSha" },\n "excludedIds": {\n "description": "Check runs left out of the count: the run the checker itself executes in.",\n "type": "array",\n "items": { "type": "integer", "minimum": 1 }\n },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "name", "status", "conclusion"],\n "additionalProperties": false,\n "dependentRequired": {\n "workflowPath": ["checkSuiteId", "workflowRunId", "runAttempt"],\n "workflowName": ["workflowPath"],\n "workflowRunId": ["workflowPath"],\n "runAttempt": ["workflowPath"]\n },\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "name": { "type": "string" },\n "status": { "type": "string", "minLength": 1 },\n "conclusion": { "type": ["string", "null"] },\n "checkSuiteId": {\n "description": "The run\'s check suite (`check_suite.id`). A run without `workflowPath` is counted as the latest per check suite and name; a run without either is keyed by its name alone. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "integer",\n "minimum": 1\n },\n "appSlug": {\n "description": "The slug of the app that created the run (`app.slug`). Recorded, not read by any check. Added in spec 0.1.4, amended 2026-10-12.",\n "type": "string",\n "minLength": 1\n },\n "workflowPath": {\n "description": "The `path` of the workflow run whose `check_suite_id` is the run\'s check suite, less any `@<ref>`, from the workflow runs at the head. Runs that have it are counted as the latest per workflow path and name. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "string",\n "minLength": 1\n },\n "workflowName": {\n "description": "The `name` of that workflow run, as the listing gives it; absent where the listing gives none, null or the empty string. No 0.1 check reads it. Added in spec 0.1.5.",\n "type": "string",\n "minLength": 1\n },\n "workflowRunId": {\n "description": "The `id` of that workflow run. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n },\n "runAttempt": {\n "description": "That workflow run\'s `run_attempt` as the listing gives it, its latest attempt at the read. Added in spec 0.1.4, amended 2026-10-13.",\n "type": "integer",\n "minimum": 1\n }\n }\n }\n }\n }\n },\n { "$ref": "#/$defs/unreadOnly" }\n ]\n },\n "testRecords": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["record", "status", "runs"],\n "additionalProperties": false,\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" },\n "status": { "const": "ok" },\n "runs": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["runId", "runAttempt", "executed", "failed"],\n "additionalProperties": false,\n "properties": {\n "runId": { "type": "integer", "minimum": 1 },\n "runAttempt": { "type": "integer", "minimum": 1 },\n "executed": { "type": "integer", "minimum": 0 },\n "failed": { "type": "integer", "minimum": 0 }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["record"],\n "properties": {\n "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" }\n },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "deployments": {\n "type": "array",\n "items": {\n "oneOf": [\n {\n "type": "object",\n "required": ["environment", "status", "deployments"],\n "additionalProperties": false,\n "properties": {\n "environment": { "type": "string", "minLength": 1 },\n "status": { "const": "ok" },\n "deployments": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["id", "sha", "relation", "successAt"],\n "additionalProperties": false,\n "properties": {\n "id": { "type": "integer", "minimum": 1 },\n "sha": { "$ref": "#/$defs/commitSha" },\n "relation": {\n "enum": ["head", "merge", "descendant", "unrelated", "unknown"]\n },\n "successAt": {\n "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }]\n }\n }\n }\n }\n }\n },\n {\n "type": "object",\n "required": ["environment"],\n "properties": { "environment": { "type": "string", "minLength": 1 } },\n "allOf": [{ "$ref": "#/$defs/unread" }]\n }\n ]\n }\n },\n "sources": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["kind", "locator", "sha256", "readAt"],\n "additionalProperties": false,\n "properties": {\n "kind": { "$ref": "#/$defs/sourceKind" },\n "locator": { "type": "string", "minLength": 1, "maxLength": 2000 },\n "sha256": { "$ref": "#/$defs/sha256" },\n "etag": { "type": "string", "minLength": 1 },\n "readAt": { "$ref": "#/$defs/timestamp" },\n "error": {\n "description": "Present when the read failed: http_<status>, network, or parse.",\n "type": "string",\n "pattern": "^(?:http_[1-5][0-9]{2}|network|parse)$"\n }\n }\n }\n }\n }\n },\n "unreadOnly": {\n "allOf": [{ "$ref": "#/$defs/unread" }],\n "unevaluatedProperties": false\n }\n }\n}\n', "record-0.2-draft.schema.json": `{
|
|
10033
10081
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
10034
10082
|
"$id": "https://barglabs.ai/dunstan/spec/schema/record-0.2-draft.schema.json",
|
|
10035
10083
|
"title": "Dunstan record, DRAFT 0.2.0",
|
|
@@ -10515,15 +10563,15 @@ var FILES = { "handback-block-0.1.schema.json": `{
|
|
|
10515
10563
|
},
|
|
10516
10564
|
"readerReason": {
|
|
10517
10565
|
"type": "string",
|
|
10518
|
-
"pattern": "^(?:no_matching_record_item|declared_item_not_among_candidates|candidate_not_in_evidence|ambiguous_candidates|workflow_not_in_record|workflow_name_ambiguous|no_comparable_record_field|file_list_truncated|checks_incomplete|all_succeeded_mismatch|test_not_executed|test_outcome_mismatch|not_closing|not_found|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request_files|check_runs|workflow_runs|pull_request_commits|closing_references|repository|issue|timeline|artifact))$"
|
|
10566
|
+
"pattern": "^(?:no_matching_record_item|declared_item_not_among_candidates|candidate_not_in_evidence|duplicate_record_item|ambiguous_candidates|workflow_not_in_record|workflow_name_ambiguous|no_comparable_record_field|file_list_truncated|checks_incomplete|all_succeeded_mismatch|test_not_executed|test_outcome_mismatch|not_closing|not_found|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request_files|check_runs|workflow_runs|pull_request_commits|closing_references|repository|issue|timeline|artifact))$"
|
|
10519
10567
|
}
|
|
10520
10568
|
}
|
|
10521
10569
|
}
|
|
10522
|
-
` };
|
|
10570
|
+
`, "survey-0.1-draft.schema.json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://barglabs.ai/dunstan/spec/schema/survey-0.1-draft.schema.json",\n "title": "Dunstan survey record, DRAFT 0.1",\n "description": "DRAFT, informative, for the operator to check (spec/claim-format.md, \\"DRAFT survey 0.1\\"). A read-only survey of a repository\'s recent pull requests: the selection, one entry per selected pull request with the record a check wrote for it or the reason there is none, and an aggregate that is a pure function of the entries. Each embedded record is a 0.1 or DRAFT 0.2.0 record and is validated against its own schema when the survey is verified. It changes neither record schema.",\n "type": "object",\n "required": ["format", "checker", "selection", "entries", "aggregate", "rerun", "digest"],\n "additionalProperties": false,\n "properties": {\n "format": { "const": "dunstan-survey-0.1-draft" },\n "checker": { "$ref": "record-0.1.schema.json#/$defs/predicate/properties/checker" },\n "selection": {\n "type": "object",\n "required": ["parameters", "reader", "listing", "selected"],\n "additionalProperties": false,\n "properties": {\n "parameters": { "$ref": "#/$defs/parameters" },\n "reader": {\n "description": "Whether the survey read GitHub with a token (GITHUB_TOKEN was set). The aggregate\'s remedy lines are worded for it. The token is never recorded.",\n "type": "object",\n "required": ["authenticated"],\n "additionalProperties": false,\n "properties": { "authenticated": { "type": "boolean" } }\n },\n "listing": { "$ref": "#/$defs/listing" },\n "selected": {\n "type": "array",\n "uniqueItems": true,\n "items": { "$ref": "#/$defs/number" }\n }\n }\n },\n "entries": {\n "description": "One per selected pull request, in selection order. Verify checks that their pull request numbers are exactly `selection.selected`, which holds no number twice.",\n "type": "array",\n "uniqueItems": true,\n "items": { "$ref": "#/$defs/entry" }\n },\n "aggregate": { "$ref": "#/$defs/aggregate" },\n "rerun": {\n "type": "object",\n "required": ["offline", "online"],\n "additionalProperties": false,\n "properties": {\n "offline": { "type": "string", "minLength": 1 },\n "online": { "type": "string", "minLength": 1 }\n }\n },\n "digest": {\n "description": "SHA-256 of the JCS bytes of the survey record without this member.",\n "type": "object",\n "required": ["sha256"],\n "additionalProperties": false,\n "properties": { "sha256": { "$ref": "record-0.1.schema.json#/$defs/sha256" } }\n }\n },\n "$defs": {\n "number": { "type": "integer", "minimum": 1 },\n "count": { "type": "integer", "minimum": 0 },\n "login": { "type": "string", "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})(?:\\\\[bot\\\\])?$" },\n "parameters": {\n "type": "object",\n "required": ["repository", "last", "since", "authors", "report", "advisory"],\n "additionalProperties": false,\n "properties": {\n "repository": { "$ref": "record-0.1.schema.json#/$defs/repository" },\n "last": { "type": "integer", "minimum": 1, "maximum": 200 },\n "since": {\n "oneOf": [\n { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },\n { "type": "null" }\n ]\n },\n "authors": { "type": "array", "items": { "$ref": "#/$defs/login" } },\n "report": {\n "oneOf": [\n {\n "type": "object",\n "required": ["kind"],\n "additionalProperties": false,\n "properties": { "kind": { "const": "pr-body" } }\n },\n {\n "type": "object",\n "required": ["kind", "author"],\n "additionalProperties": false,\n "properties": {\n "kind": { "const": "pr-comment" },\n "author": { "$ref": "#/$defs/login" }\n }\n }\n ]\n },\n "advisory": { "type": "boolean" }\n }\n },\n "listing": {\n "type": "object",\n "required": ["pageBound", "pages", "pullRequests", "duplicates", "end"],\n "additionalProperties": false,\n "properties": {\n "pageBound": { "type": "integer", "minimum": 1 },\n "pages": {\n "type": "array",\n "minItems": 1,\n "items": {\n "type": "object",\n "required": ["page", "locator", "sha256", "readAt", "items", "duplicates"],\n "additionalProperties": false,\n "properties": {\n "page": { "$ref": "#/$defs/number" },\n "locator": { "type": "string", "minLength": 1 },\n "sha256": { "$ref": "record-0.1.schema.json#/$defs/sha256" },\n "readAt": { "$ref": "record-0.1.schema.json#/$defs/timestamp" },\n "items": { "$ref": "#/$defs/count" },\n "duplicates": {\n "description": "How many of this page\'s items repeat a pull request number an earlier item of the listing already held. They are not in `pullRequests`.",\n "$ref": "#/$defs/count"\n },\n "error": { "type": "string", "pattern": "^(?:http_[1-5][0-9]{2}|network|parse)$" }\n }\n }\n },\n "duplicates": {\n "description": "How many listed items repeated an earlier one: the sum of the pages\' duplicates. The pages\' items are the length of `pullRequests` plus this.",\n "$ref": "#/$defs/count"\n },\n "pullRequests": {\n "description": "Each listed pull request once, at its first occurrence, in listing order. Verify refuses a number listed twice.",\n "type": "array",\n "uniqueItems": true,\n "items": {\n "type": "object",\n "required": ["number", "createdAt", "author", "merged"],\n "additionalProperties": false,\n "properties": {\n "number": { "$ref": "#/$defs/number" },\n "createdAt": { "$ref": "record-0.1.schema.json#/$defs/timestamp" },\n "author": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] },\n "merged": { "type": "boolean" }\n }\n }\n },\n "end": {\n "enum": [\n "selection_full",\n "since_reached",\n "listing_ended",\n "listing_unreadable",\n "page_bound"\n ]\n }\n }\n },\n "noRecordReason": {\n "enum": [\n "pull_request_unreadable",\n "pull_request_moved",\n "rate_limited",\n "report_unavailable"\n ]\n },\n "entry": {\n "oneOf": [\n {\n "type": "object",\n "required": ["pullRequest", "record"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": { "$ref": "#/$defs/number" },\n "record": {\n "description": "The record `dunstan check` writes for this pull request, as written: a 0.1 record, or a DRAFT 0.2.0 record when the survey ran the advisory reader. Verify validates it against its own schema.",\n "type": "object"\n }\n }\n },\n {\n "type": "object",\n "required": ["pullRequest", "record", "reason"],\n "additionalProperties": false,\n "properties": {\n "pullRequest": { "$ref": "#/$defs/number" },\n "record": { "type": "null" },\n "reason": { "$ref": "#/$defs/noRecordReason" }\n }\n }\n ]\n },\n "columns": {\n "type": "object",\n "required": ["supported", "unverifiable", "notRecordCheckable", "contradicted"],\n "properties": {\n "supported": { "$ref": "#/$defs/count" },\n "unverifiable": { "$ref": "#/$defs/count" },\n "notRecordCheckable": { "$ref": "#/$defs/count" },\n "contradicted": { "$ref": "#/$defs/count" }\n }\n },\n "fixed": { "type": "string", "pattern": "^[01]\\\\.[0-9]{4}$" },\n "rate": {\n "type": "object",\n "required": ["numerator", "denominator", "value", "wilson95"],\n "additionalProperties": false,\n "properties": {\n "numerator": { "$ref": "#/$defs/count" },\n "denominator": { "$ref": "#/$defs/count" },\n "value": { "oneOf": [{ "$ref": "#/$defs/fixed" }, { "type": "null" }] },\n "wilson95": {\n "oneOf": [\n {\n "type": "object",\n "required": ["low", "high"],\n "additionalProperties": false,\n "properties": {\n "low": { "$ref": "#/$defs/fixed" },\n "high": { "$ref": "#/$defs/fixed" }\n }\n },\n { "type": "null" }\n ]\n }\n }\n },\n "aggregate": {\n "type": "object",\n "required": [\n "mapping",\n "pullRequests",\n "claims",\n "byType",\n "cannotSettle",\n "contradictedOfSettled",\n "unsettled"\n ],\n "additionalProperties": false,\n "properties": {\n "mapping": {\n "type": "object",\n "required": ["version"],\n "additionalProperties": false,\n "properties": { "version": { "type": "string", "minLength": 1 } }\n },\n "pullRequests": {\n "type": "object",\n "required": [\n "listed",\n "selected",\n "withRecord",\n "blockFound",\n "blockMissing",\n "blockInvalid",\n "blockAmbiguous",\n "noRecord"\n ],\n "additionalProperties": false,\n "properties": {\n "listed": { "$ref": "#/$defs/count" },\n "selected": { "$ref": "#/$defs/count" },\n "withRecord": { "$ref": "#/$defs/count" },\n "blockFound": { "$ref": "#/$defs/count" },\n "blockMissing": { "$ref": "#/$defs/count" },\n "blockInvalid": { "$ref": "#/$defs/count" },\n "blockAmbiguous": { "$ref": "#/$defs/count" },\n "noRecord": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["reason", "count"],\n "additionalProperties": false,\n "properties": {\n "reason": { "$ref": "#/$defs/noRecordReason" },\n "count": { "$ref": "#/$defs/count" }\n }\n }\n }\n }\n },\n "claims": {\n "allOf": [{ "$ref": "#/$defs/columns" }],\n "required": ["total"],\n "properties": { "total": { "$ref": "#/$defs/count" } },\n "unevaluatedProperties": false\n },\n "byType": {\n "type": "array",\n "items": {\n "allOf": [{ "$ref": "#/$defs/columns" }],\n "required": ["type"],\n "properties": {\n "type": { "enum": ["T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "R"] }\n },\n "unevaluatedProperties": false\n }\n },\n "cannotSettle": { "$ref": "#/$defs/rate" },\n "contradictedOfSettled": { "$ref": "#/$defs/rate" },\n "unsettled": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["reason", "column", "count", "remedy"],\n "additionalProperties": false,\n "properties": {\n "reason": { "$ref": "record-0.1.schema.json#/$defs/claimReason" },\n "column": { "enum": ["unverifiable", "notRecordCheckable"] },\n "count": { "type": "integer", "minimum": 1 },\n "remedy": { "oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] }\n }\n }\n },\n "advisory": {\n "type": "object",\n "required": ["pullRequests", "notes"],\n "additionalProperties": false,\n "properties": {\n "pullRequests": { "$ref": "#/$defs/count" },\n "notes": {\n "type": "array",\n "items": {\n "type": "object",\n "required": ["note", "count"],\n "additionalProperties": false,\n "properties": {\n "note": { "type": "string", "minLength": 1 },\n "count": { "type": "integer", "minimum": 1 }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n' };
|
|
10523
10571
|
function schemaText(file2) {
|
|
10524
|
-
const
|
|
10525
|
-
if (
|
|
10526
|
-
return
|
|
10572
|
+
const text2 = FILES[file2];
|
|
10573
|
+
if (text2 === void 0) throw new Error(`no schema ${file2} in this build`);
|
|
10574
|
+
return text2;
|
|
10527
10575
|
}
|
|
10528
10576
|
|
|
10529
10577
|
// src/spec/schema-validators.ts
|
|
@@ -10592,30 +10640,40 @@ function validateDraftRecord(value) {
|
|
|
10592
10640
|
}
|
|
10593
10641
|
return draftRecord(value) ? [] : toSchemaErrors(draftRecord.errors);
|
|
10594
10642
|
}
|
|
10643
|
+
var SURVEY_SCHEMA_FILE = "survey-0.1-draft.schema.json";
|
|
10644
|
+
var surveyRecord;
|
|
10645
|
+
function validateSurveyRecord(value) {
|
|
10646
|
+
if (surveyRecord === void 0) {
|
|
10647
|
+
const ajv = createAjv();
|
|
10648
|
+
ajv.addSchema(readSchema(SURVEY_SCHEMA_FILE));
|
|
10649
|
+
surveyRecord = ajv.getSchema(schemaId(SURVEY_SCHEMA_FILE));
|
|
10650
|
+
}
|
|
10651
|
+
return surveyRecord(value) ? [] : toSchemaErrors(surveyRecord.errors);
|
|
10652
|
+
}
|
|
10595
10653
|
|
|
10596
10654
|
// src/spec/extract.ts
|
|
10597
10655
|
var OPENING_FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
10598
|
-
function isClosingFence(
|
|
10599
|
-
const match = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(
|
|
10656
|
+
function isClosingFence(line2, fence) {
|
|
10657
|
+
const match = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line2);
|
|
10600
10658
|
const run = match?.[1];
|
|
10601
10659
|
return run !== void 0 && run[0] === fence.char && run.length >= fence.length;
|
|
10602
10660
|
}
|
|
10603
10661
|
function scanHandbackBlocks(reportText) {
|
|
10604
|
-
const
|
|
10605
|
-
const lines =
|
|
10662
|
+
const text2 = reportText.startsWith("\uFEFF") ? reportText.slice(1) : reportText;
|
|
10663
|
+
const lines = text2.replace(/\r\n?/g, "\n").split("\n");
|
|
10606
10664
|
const blocks = [];
|
|
10607
10665
|
let open2;
|
|
10608
|
-
for (const
|
|
10666
|
+
for (const line2 of lines) {
|
|
10609
10667
|
if (open2 !== void 0) {
|
|
10610
|
-
if (isClosingFence(
|
|
10668
|
+
if (isClosingFence(line2, open2)) {
|
|
10611
10669
|
if (open2.handback) blocks.push({ content: open2.lines.join("\n"), terminated: true });
|
|
10612
10670
|
open2 = void 0;
|
|
10613
10671
|
} else if (open2.handback) {
|
|
10614
|
-
open2.lines.push(
|
|
10672
|
+
open2.lines.push(line2);
|
|
10615
10673
|
}
|
|
10616
10674
|
continue;
|
|
10617
10675
|
}
|
|
10618
|
-
const match = OPENING_FENCE.exec(
|
|
10676
|
+
const match = OPENING_FENCE.exec(line2);
|
|
10619
10677
|
const run = match?.[1];
|
|
10620
10678
|
if (run === void 0) continue;
|
|
10621
10679
|
const info = (match?.[2] ?? "").replace(/^[ \t]+|[ \t]+$/g, "");
|
|
@@ -10724,29 +10782,83 @@ function defaultBase(cwd) {
|
|
|
10724
10782
|
}
|
|
10725
10783
|
return ref.slice("refs/remotes/".length);
|
|
10726
10784
|
}
|
|
10727
|
-
function
|
|
10728
|
-
const path = resolve(cwd, tests.junit);
|
|
10729
|
-
let counts;
|
|
10785
|
+
function localCounts(cwd, flag, file2) {
|
|
10730
10786
|
try {
|
|
10731
|
-
|
|
10787
|
+
return countJunit(readFileSync2(resolve(cwd, file2), "utf8"));
|
|
10732
10788
|
} catch (e) {
|
|
10733
|
-
throw new TemplateError(
|
|
10789
|
+
throw new TemplateError(`${flag} ${file2}: ${e.message}`);
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
function testsFrom(cwd, tests) {
|
|
10793
|
+
if (tests.kind === "ci") {
|
|
10794
|
+
return tests.read.status === "settled" ? { run: tests.read.run, notes: tests.read.notes } : { notes: tests.read.notes };
|
|
10795
|
+
}
|
|
10796
|
+
if (tests.kind === "local") {
|
|
10797
|
+
const counts2 = localCounts(cwd, "--tests-local", tests.junit);
|
|
10798
|
+
return {
|
|
10799
|
+
run: {
|
|
10800
|
+
command: tests.command,
|
|
10801
|
+
count: counts2.executed,
|
|
10802
|
+
failures: counts2.failed,
|
|
10803
|
+
record: { kind: "local" }
|
|
10804
|
+
},
|
|
10805
|
+
notes: [
|
|
10806
|
+
`tests: ${counts2.executed} executed and ${counts2.failed} failed in ${tests.junit}, declared as a local run (record {"kind": "local"}). The gate reports them unverifiable (local_run_not_in_record) until CI publishes a record: publish one (docs/ci-test-records.md), then run dunstan template --tests-ci once CI has finished. Until then, say in the report that CI publishes no test record, and state no count in prose that the block does not declare.`
|
|
10807
|
+
]
|
|
10808
|
+
};
|
|
10734
10809
|
}
|
|
10810
|
+
const counts = localCounts(cwd, "--tests-junit", tests.junit);
|
|
10811
|
+
const r = tests.record;
|
|
10735
10812
|
return {
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10813
|
+
run: {
|
|
10814
|
+
command: tests.command,
|
|
10815
|
+
count: counts.executed,
|
|
10816
|
+
failures: counts.failed,
|
|
10817
|
+
record: { kind: "junit", ...r }
|
|
10818
|
+
},
|
|
10819
|
+
notes: [
|
|
10820
|
+
`tests: ${counts.executed} executed and ${counts.failed} failed in ${tests.junit}. The gate checks these counts against ${r.path} in the artifact ${r.artifact} of job ${r.job} in ${r.workflow} at the head commit, not against the local file: these local counts will be compared with CI's, and fail with count_mismatch where CI's run counted otherwise. dunstan template --tests-ci declares CI's counts instead.`
|
|
10821
|
+
]
|
|
10740
10822
|
};
|
|
10741
10823
|
}
|
|
10742
|
-
function
|
|
10743
|
-
const
|
|
10824
|
+
function originRepository(cwd) {
|
|
10825
|
+
const r = git(cwd, ["remote", "get-url", "origin"]);
|
|
10826
|
+
const url2 = r.stdout.trim();
|
|
10827
|
+
if (!r.ok || url2 === "") {
|
|
10828
|
+
throw new TemplateError("there is no origin remote: pass --repo <owner/name>");
|
|
10829
|
+
}
|
|
10830
|
+
const m = /^(?:https:\/\/(?:[^@/]+@)?|ssh:\/\/git@|git@)github\.com[:/]([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/.exec(
|
|
10831
|
+
url2
|
|
10832
|
+
);
|
|
10833
|
+
if (m === null) {
|
|
10834
|
+
throw new TemplateError(
|
|
10835
|
+
`origin ${url2} is not a github.com repository: pass --repo <owner/name>`
|
|
10836
|
+
);
|
|
10837
|
+
}
|
|
10838
|
+
return `${m[1]}/${m[2]}`;
|
|
10839
|
+
}
|
|
10840
|
+
function isPushed(cwd, commit) {
|
|
10841
|
+
const r = git(cwd, [
|
|
10842
|
+
"for-each-ref",
|
|
10843
|
+
"--contains",
|
|
10844
|
+
commit,
|
|
10845
|
+
"--format=%(refname)",
|
|
10846
|
+
"refs/remotes/"
|
|
10847
|
+
]);
|
|
10848
|
+
return r.ok && r.stdout.trim() !== "";
|
|
10849
|
+
}
|
|
10850
|
+
function headCommit(cwd) {
|
|
10744
10851
|
const top = git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
10745
10852
|
if (!top.ok) throw new TemplateError(`not a git repository: ${cwd}`);
|
|
10746
10853
|
const head = commitOf(cwd, "HEAD");
|
|
10747
10854
|
if (head === void 0) {
|
|
10748
10855
|
throw new TemplateError("HEAD cannot be read: the branch has no commit yet");
|
|
10749
10856
|
}
|
|
10857
|
+
return head;
|
|
10858
|
+
}
|
|
10859
|
+
function templateBlock(options) {
|
|
10860
|
+
const { cwd } = options;
|
|
10861
|
+
const head = headCommit(cwd);
|
|
10750
10862
|
const symbolic = git(cwd, ["symbolic-ref", "--quiet", "HEAD"]);
|
|
10751
10863
|
if (!symbolic.ok) {
|
|
10752
10864
|
throw new TemplateError(
|
|
@@ -10778,7 +10890,8 @@ function templateBlock(options) {
|
|
|
10778
10890
|
compareCodeUnits
|
|
10779
10891
|
);
|
|
10780
10892
|
const block = { dunstan: BLOCK_VERSION, headCommit: head, filesChanged };
|
|
10781
|
-
|
|
10893
|
+
const tests = options.tests === void 0 ? void 0 : testsFrom(cwd, options.tests);
|
|
10894
|
+
if (tests?.run !== void 0) block.tests = [tests.run];
|
|
10782
10895
|
const read = readBlockContent(JSON.stringify(block));
|
|
10783
10896
|
if (read.status !== "found") {
|
|
10784
10897
|
const errors = read.status === "invalid" ? read.errors : [];
|
|
@@ -10794,16 +10907,12 @@ function templateBlock(options) {
|
|
|
10794
10907
|
if (dirty.ok && dirty.stdout !== "") {
|
|
10795
10908
|
notes.push("the working tree has changes that are not committed; they are not in this block.");
|
|
10796
10909
|
}
|
|
10797
|
-
|
|
10798
|
-
if (t === void 0) {
|
|
10910
|
+
if (tests === void 0) {
|
|
10799
10911
|
notes.push(
|
|
10800
|
-
"tests are not declared. To declare a run,
|
|
10912
|
+
"tests are not declared. To declare CI's counts, run dunstan template again with --tests-ci --tests-command <cmd> --tests-workflow <path> --tests-job <name> once CI has finished on your last push; it reads the JUnit record CI published (docs/ci-test-records.md). For a run made only locally, --tests-local <JUnit XML file> --tests-command <cmd> declares it as one. --tests-junit <file> with --tests-command, --tests-workflow, --tests-job, --tests-artifact and --tests-artifact-path declares a local file's counts against a CI record."
|
|
10801
10913
|
);
|
|
10802
10914
|
} else {
|
|
10803
|
-
|
|
10804
|
-
notes.push(
|
|
10805
|
-
`tests: ${t.count} executed and ${t.failures} failed in ${options.tests?.junit}. The gate checks these counts against ${r.path} in the artifact ${r.artifact} of job ${r.job} in ${r.workflow} at the head commit, not against the local file.`
|
|
10806
|
-
);
|
|
10915
|
+
notes.push(...tests.notes);
|
|
10807
10916
|
}
|
|
10808
10917
|
notes.push(
|
|
10809
10918
|
"references and mergedAt are not filled: add each issue this pull request closes or cites yourself, and leave mergedAt out until it is merged.",
|
|
@@ -10846,7 +10955,7 @@ var GitHubClient = class {
|
|
|
10846
10955
|
this.#baseUrl = options.baseUrl ?? DEFAULT_API;
|
|
10847
10956
|
this.#fetch = options.fetch ?? fetch;
|
|
10848
10957
|
this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
10849
|
-
this.#sleep = options.sleep ?? ((ms) => new Promise((
|
|
10958
|
+
this.#sleep = options.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
|
|
10850
10959
|
this.#warn = options.warn ?? ((message) => process.stderr.write(`dunstan: ${message}
|
|
10851
10960
|
`));
|
|
10852
10961
|
this.#maxAttempts = options.maxAttempts ?? 4;
|
|
@@ -11007,8 +11116,8 @@ function retryWaitMs(response, status, now, attempt) {
|
|
|
11007
11116
|
const retryAfter = response.headers.get("retry-after");
|
|
11008
11117
|
const remaining = response.headers.get("x-ratelimit-remaining");
|
|
11009
11118
|
const reset = response.headers.get("x-ratelimit-reset");
|
|
11010
|
-
const
|
|
11011
|
-
if (
|
|
11119
|
+
const rateLimited2 = status === 429 || status === 403 && (remaining === "0" || retryAfter !== null);
|
|
11120
|
+
if (rateLimited2) {
|
|
11012
11121
|
if (retryAfter !== null && /^[0-9]+$/.test(retryAfter)) return Number(retryAfter) * 1e3;
|
|
11013
11122
|
if (reset !== null && /^[0-9]+$/.test(reset)) {
|
|
11014
11123
|
return Math.max(0, Number(reset) * 1e3 - now.getTime()) + 1e3;
|
|
@@ -11659,22 +11768,22 @@ function answersFor(paths, answered) {
|
|
|
11659
11768
|
});
|
|
11660
11769
|
}
|
|
11661
11770
|
function compareByList(claim2, evidence, repository) {
|
|
11662
|
-
const
|
|
11663
|
-
if (
|
|
11664
|
-
const observed =
|
|
11665
|
-
if (claim2.kind === "file_changed" && typeof claim2.value === "string" &&
|
|
11771
|
+
const row2 = rowFor(claim2.kind, claim2.value, evidence, repository);
|
|
11772
|
+
if (row2 === void 0) return { observed: null, note: "unanswered:no_comparable_record_field" };
|
|
11773
|
+
const observed = row2.observed ?? null;
|
|
11774
|
+
if (claim2.kind === "file_changed" && typeof claim2.value === "string" && row2.verdict !== "unverifiable") {
|
|
11666
11775
|
const bare = !claim2.value.includes("/");
|
|
11667
|
-
if (bare ||
|
|
11776
|
+
if (bare || row2.verdict === "fail") {
|
|
11668
11777
|
const candidates = byName(claim2.value, evidence);
|
|
11669
11778
|
if (candidates.length === 1)
|
|
11670
11779
|
return { observed: candidates[0], note: "agrees_by_name" };
|
|
11671
11780
|
if (candidates.length > 1) return { observed: candidates, note: "unanswered:ambiguous_path" };
|
|
11672
11781
|
}
|
|
11673
11782
|
}
|
|
11674
|
-
if (
|
|
11783
|
+
if (row2.verdict === "pass") return { observed, note: "agrees" };
|
|
11675
11784
|
return {
|
|
11676
11785
|
observed,
|
|
11677
|
-
note: `${
|
|
11786
|
+
note: `${row2.verdict === "fail" ? "differs" : "unanswered"}:${row2.reason}`
|
|
11678
11787
|
};
|
|
11679
11788
|
}
|
|
11680
11789
|
function readingBlock(block, proposed, headSha) {
|
|
@@ -11801,8 +11910,8 @@ var FINAL_STATES = phrases(GRAMMAR.finalStates);
|
|
|
11801
11910
|
var CLOSERS = new Map(GRAMMAR.brackets.map((pair) => [pair[1], pair[0]]));
|
|
11802
11911
|
var OPENERS = new Set(CLOSERS.values());
|
|
11803
11912
|
var WINDOW = GRAMMAR.window;
|
|
11804
|
-
function isLogLine(
|
|
11805
|
-
const body =
|
|
11913
|
+
function isLogLine(line2) {
|
|
11914
|
+
const body = line2.replace(/^\s*(?:[-*+]|\d{1,3}[.)])\s+/, "").trimStart();
|
|
11806
11915
|
if (/^(?:\$\s|\[[^\]]*\]|\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}|[✓✗×✔✘❯›])/.test(body)) return true;
|
|
11807
11916
|
if (/^(?:error|warning|Error|Warning)(?:\s+TS\d+)?:/.test(body)) return true;
|
|
11808
11917
|
const first = /^[^\s:]+/.exec(body)?.[0] ?? "";
|
|
@@ -11815,32 +11924,32 @@ function prepare(report) {
|
|
|
11815
11924
|
};
|
|
11816
11925
|
let offset = 0;
|
|
11817
11926
|
let fence;
|
|
11818
|
-
for (const
|
|
11927
|
+
for (const line2 of report.split("\n")) {
|
|
11819
11928
|
const start = offset;
|
|
11820
|
-
const end = offset +
|
|
11929
|
+
const end = offset + line2.length;
|
|
11821
11930
|
offset = end + 1;
|
|
11822
11931
|
if (fence !== void 0) {
|
|
11823
11932
|
blank(start, end);
|
|
11824
|
-
const close = /^ {0,3}(`{3,}|~{3,})\s*$/.exec(
|
|
11933
|
+
const close = /^ {0,3}(`{3,}|~{3,})\s*$/.exec(line2)?.[1];
|
|
11825
11934
|
if (close !== void 0 && close[0] === fence.char && close.length >= fence.length) {
|
|
11826
11935
|
fence = void 0;
|
|
11827
11936
|
}
|
|
11828
11937
|
continue;
|
|
11829
11938
|
}
|
|
11830
|
-
const open2 = /^ {0,3}(`{3,}|~{3,})/.exec(
|
|
11939
|
+
const open2 = /^ {0,3}(`{3,}|~{3,})/.exec(line2)?.[1];
|
|
11831
11940
|
if (open2 !== void 0) {
|
|
11832
11941
|
fence = { char: open2[0], length: open2.length };
|
|
11833
11942
|
blank(start, end);
|
|
11834
|
-
} else if (/^ {0,3}>/.test(
|
|
11943
|
+
} else if (/^ {0,3}>/.test(line2) || isLogLine(line2)) {
|
|
11835
11944
|
blank(start, end);
|
|
11836
11945
|
}
|
|
11837
11946
|
}
|
|
11838
|
-
let
|
|
11839
|
-
for (const m of
|
|
11840
|
-
|
|
11947
|
+
let text2 = chars.join("");
|
|
11948
|
+
for (const m of text2.matchAll(/<!--[\s\S]*?(?:-->|$)/g)) blank(m.index, m.index + m[0].length);
|
|
11949
|
+
text2 = chars.join("");
|
|
11841
11950
|
const atoms = /* @__PURE__ */ new Map();
|
|
11842
|
-
const inAtom = new Uint8Array(
|
|
11843
|
-
for (const m of
|
|
11951
|
+
const inAtom = new Uint8Array(text2.length);
|
|
11952
|
+
for (const m of text2.matchAll(/`([^`\n]+)`/g)) {
|
|
11844
11953
|
const content = m[1];
|
|
11845
11954
|
const lead = content.length - content.trimStart().length;
|
|
11846
11955
|
const trimmed = content.trim();
|
|
@@ -11851,13 +11960,13 @@ function prepare(report) {
|
|
|
11851
11960
|
atoms.set(start, { end: start + trimmed.length, kind: "code" });
|
|
11852
11961
|
inAtom.fill(1, start, start + trimmed.length);
|
|
11853
11962
|
}
|
|
11854
|
-
|
|
11855
|
-
for (const m of
|
|
11963
|
+
text2 = chars.join("");
|
|
11964
|
+
for (const m of text2.matchAll(/\bhttps?:\/\/[^\s<>()[\]`"']+/g)) {
|
|
11856
11965
|
if (inAtom[m.index] === 1) continue;
|
|
11857
11966
|
const url2 = m[0].replace(/[.,;:!?]+$/, "");
|
|
11858
11967
|
atoms.set(m.index, { end: m.index + url2.length, kind: "url" });
|
|
11859
11968
|
}
|
|
11860
|
-
return { text, atoms };
|
|
11969
|
+
return { text: text2, atoms };
|
|
11861
11970
|
}
|
|
11862
11971
|
var LEADING = /^[(["'*<{~]+/;
|
|
11863
11972
|
var TRAILING = /[)\]}"'*>~,.;:!?]+$/;
|
|
@@ -11868,23 +11977,23 @@ function makeWord(raw, start, atom, bracketAt) {
|
|
|
11868
11977
|
return { text: raw, lower: "", start, end: rawEnd, rawEnd, bracket, comma: false, atom };
|
|
11869
11978
|
}
|
|
11870
11979
|
const lead = LEADING.exec(raw)?.[0].length ?? 0;
|
|
11871
|
-
const
|
|
11872
|
-
if (
|
|
11980
|
+
const text2 = raw.slice(lead).replace(TRAILING, "");
|
|
11981
|
+
if (text2 === "") return void 0;
|
|
11873
11982
|
const begin = start + lead;
|
|
11874
11983
|
return {
|
|
11875
|
-
text,
|
|
11876
|
-
lower:
|
|
11984
|
+
text: text2,
|
|
11985
|
+
lower: text2.toLowerCase(),
|
|
11877
11986
|
start: begin,
|
|
11878
|
-
end: begin +
|
|
11987
|
+
end: begin + text2.length,
|
|
11879
11988
|
rawEnd,
|
|
11880
11989
|
bracket: bracketAt(begin),
|
|
11881
|
-
comma: raw.slice(lead +
|
|
11990
|
+
comma: raw.slice(lead + text2.length).includes(",")
|
|
11882
11991
|
};
|
|
11883
11992
|
}
|
|
11884
11993
|
function bracketCursor(prep) {
|
|
11885
|
-
const { text, atoms } = prep;
|
|
11994
|
+
const { text: text2, atoms } = prep;
|
|
11886
11995
|
const links = /* @__PURE__ */ new Set();
|
|
11887
|
-
for (const m of
|
|
11996
|
+
for (const m of text2.matchAll(/\[[^[\]\n]*\]\(/g)) {
|
|
11888
11997
|
links.add(m.index);
|
|
11889
11998
|
links.add(m.index + m[0].length - 2);
|
|
11890
11999
|
}
|
|
@@ -11897,10 +12006,10 @@ function bracketCursor(prep) {
|
|
|
11897
12006
|
p = atom.end;
|
|
11898
12007
|
continue;
|
|
11899
12008
|
}
|
|
11900
|
-
const ch =
|
|
12009
|
+
const ch = text2[p];
|
|
11901
12010
|
if (!links.has(p)) {
|
|
11902
12011
|
if (OPENERS.has(ch)) open2.push(p);
|
|
11903
|
-
else if (CLOSERS.has(ch) &&
|
|
12012
|
+
else if (CLOSERS.has(ch) && text2[open2.at(-1) ?? -1] === CLOSERS.get(ch)) open2.pop();
|
|
11904
12013
|
}
|
|
11905
12014
|
p++;
|
|
11906
12015
|
}
|
|
@@ -11908,7 +12017,7 @@ function bracketCursor(prep) {
|
|
|
11908
12017
|
};
|
|
11909
12018
|
}
|
|
11910
12019
|
function clausesOf(prep) {
|
|
11911
|
-
const { text, atoms } = prep;
|
|
12020
|
+
const { text: text2, atoms } = prep;
|
|
11912
12021
|
const bracketAt = bracketCursor(prep);
|
|
11913
12022
|
const clauses = [];
|
|
11914
12023
|
let current = [];
|
|
@@ -11925,20 +12034,20 @@ function clausesOf(prep) {
|
|
|
11925
12034
|
let blockLine2 = false;
|
|
11926
12035
|
let i = 0;
|
|
11927
12036
|
let gapStart = 0;
|
|
11928
|
-
while (i <
|
|
12037
|
+
while (i < text2.length) {
|
|
11929
12038
|
const atom = atoms.get(i);
|
|
11930
|
-
if (atom === void 0 && /\s/.test(
|
|
12039
|
+
if (atom === void 0 && /\s/.test(text2[i])) {
|
|
11931
12040
|
i++;
|
|
11932
12041
|
continue;
|
|
11933
12042
|
}
|
|
11934
12043
|
let end = atom?.end ?? i;
|
|
11935
12044
|
if (atom === void 0) {
|
|
11936
|
-
while (end <
|
|
12045
|
+
while (end < text2.length && !/\s/.test(text2[end]) && !atoms.has(end)) end++;
|
|
11937
12046
|
}
|
|
11938
|
-
const gap =
|
|
12047
|
+
const gap = text2.slice(gapStart, i);
|
|
11939
12048
|
let back = i;
|
|
11940
|
-
while (back > 0 && /[ \t\r]/.test(
|
|
11941
|
-
const lineStart = back === 0 ||
|
|
12049
|
+
while (back > 0 && /[ \t\r]/.test(text2[back - 1])) back--;
|
|
12050
|
+
const lineStart = back === 0 || text2[back - 1] === "\n";
|
|
11942
12051
|
if (/\n[ \t\r]*\n/.test(gap)) {
|
|
11943
12052
|
flush();
|
|
11944
12053
|
item = void 0;
|
|
@@ -11947,14 +12056,14 @@ function clausesOf(prep) {
|
|
|
11947
12056
|
if (blockLine2) flush();
|
|
11948
12057
|
blockLine2 = false;
|
|
11949
12058
|
}
|
|
11950
|
-
const raw =
|
|
12059
|
+
const raw = text2.slice(i, end);
|
|
11951
12060
|
if (atom === void 0) {
|
|
11952
12061
|
const marker = lineStart && /^(?:[-*+]|\d{1,3}[.)]|#{1,6})$/.test(raw);
|
|
11953
12062
|
if (marker || /^\|+$/.test(raw)) {
|
|
11954
12063
|
flush();
|
|
11955
12064
|
if (lineStart && /^[#|]/.test(raw)) blockLine2 = true;
|
|
11956
12065
|
if (marker && !raw.startsWith("#")) {
|
|
11957
|
-
item = { column: i - (
|
|
12066
|
+
item = { column: i - (text2.lastIndexOf("\n", i - 1) + 1) };
|
|
11958
12067
|
fresh = true;
|
|
11959
12068
|
} else {
|
|
11960
12069
|
item = void 0;
|
|
@@ -12116,8 +12225,8 @@ var Clause = class {
|
|
|
12116
12225
|
function clauseText(report, words2) {
|
|
12117
12226
|
const first = words2[0];
|
|
12118
12227
|
const last = words2[words2.length - 1];
|
|
12119
|
-
const
|
|
12120
|
-
return
|
|
12228
|
+
const text2 = report.slice(first.start, last.rawEnd).replace(/\s+/g, " ").trim();
|
|
12229
|
+
return text2.length > 300 ? `${text2.slice(0, 299)}\u2026` : text2;
|
|
12121
12230
|
}
|
|
12122
12231
|
function bindPath(c, j, value, emit) {
|
|
12123
12232
|
for (let k2 = j - 1; k2 >= Math.max(0, j - 3) && !c.isVerb(k2); k2--) {
|
|
@@ -12249,9 +12358,9 @@ function subjectOf(c, r, k) {
|
|
|
12249
12358
|
}
|
|
12250
12359
|
function baselineBefore(c, j) {
|
|
12251
12360
|
for (let k = 0; k < j; k++) {
|
|
12252
|
-
const
|
|
12361
|
+
const text2 = c.words[k].text.toLowerCase();
|
|
12253
12362
|
if (BASELINES.has(c.lower(k))) return true;
|
|
12254
|
-
if (GRAMMAR.remotePrefixes.some((p) =>
|
|
12363
|
+
if (GRAMMAR.remotePrefixes.some((p) => text2.startsWith(p))) return true;
|
|
12255
12364
|
}
|
|
12256
12365
|
return false;
|
|
12257
12366
|
}
|
|
@@ -12519,85 +12628,85 @@ function claimTexts(claim2) {
|
|
|
12519
12628
|
function boundedAt(haystack, needle, inside) {
|
|
12520
12629
|
const out = [];
|
|
12521
12630
|
if (needle.length === 0) return out;
|
|
12522
|
-
for (let
|
|
12523
|
-
const before = haystack[
|
|
12524
|
-
const after = haystack[
|
|
12525
|
-
const next = haystack[
|
|
12631
|
+
for (let at2 = haystack.indexOf(needle); at2 !== -1; at2 = haystack.indexOf(needle, at2 + 1)) {
|
|
12632
|
+
const before = haystack[at2 - 1];
|
|
12633
|
+
const after = haystack[at2 + needle.length];
|
|
12634
|
+
const next = haystack[at2 + needle.length + 1];
|
|
12526
12635
|
const openBefore = before === void 0 || !inside.test(before);
|
|
12527
12636
|
const openAfter = after === void 0 || !inside.test(after) || after === "." && (next === void 0 || !/[A-Za-z0-9]/.test(next));
|
|
12528
|
-
if (openBefore && openAfter) out.push(
|
|
12637
|
+
if (openBefore && openAfter) out.push(at2);
|
|
12529
12638
|
}
|
|
12530
12639
|
return out;
|
|
12531
12640
|
}
|
|
12532
12641
|
function occursBounded(haystack, needle, inside) {
|
|
12533
12642
|
return boundedAt(haystack, needle, inside).length > 0;
|
|
12534
12643
|
}
|
|
12535
|
-
function classnameSpans(
|
|
12644
|
+
function classnameSpans(text2, classname) {
|
|
12536
12645
|
const forms = /* @__PURE__ */ new Set([classname, classname.replaceAll(".", " ")]);
|
|
12537
12646
|
return [...forms].flatMap(
|
|
12538
|
-
(form) => boundedAt(
|
|
12647
|
+
(form) => boundedAt(text2, form, WORD).map((at2) => [at2, at2 + form.length])
|
|
12539
12648
|
);
|
|
12540
12649
|
}
|
|
12541
|
-
function classnameNamed(
|
|
12542
|
-
const longer = classnames.filter((c) => c.length > classname.length).flatMap((c) => classnameSpans(
|
|
12543
|
-
return classnameSpans(
|
|
12650
|
+
function classnameNamed(text2, classname, classnames) {
|
|
12651
|
+
const longer = classnames.filter((c) => c.length > classname.length).flatMap((c) => classnameSpans(text2, c));
|
|
12652
|
+
return classnameSpans(text2, classname).some(
|
|
12544
12653
|
([from, to]) => !longer.some(([l, r]) => l <= from && to <= r)
|
|
12545
12654
|
);
|
|
12546
12655
|
}
|
|
12547
12656
|
var CHECK_RUN_EVENTS = ["push", "pull_request", "pull_request_target", "merge_group"];
|
|
12548
12657
|
var EVENT_SUFFIX = new RegExp(`^ \\((?:${CHECK_RUN_EVENTS.join("|")})\\)`);
|
|
12549
12658
|
var WORKFLOW_JOB = /(\S+) \/ /g;
|
|
12550
|
-
function afterWorkflows(
|
|
12551
|
-
return [...
|
|
12552
|
-
}
|
|
12553
|
-
function jobNamed(
|
|
12554
|
-
if (
|
|
12555
|
-
const workflowed = afterWorkflows(
|
|
12556
|
-
return boundedAt(
|
|
12557
|
-
(
|
|
12659
|
+
function afterWorkflows(text2) {
|
|
12660
|
+
return [...text2.matchAll(WORKFLOW_JOB)].map((m) => m.index + m[0].length);
|
|
12661
|
+
}
|
|
12662
|
+
function jobNamed(text2, name, longer) {
|
|
12663
|
+
if (text2 === name) return true;
|
|
12664
|
+
const workflowed = afterWorkflows(text2);
|
|
12665
|
+
return boundedAt(text2, name, JOB_NAME_CHAR).some(
|
|
12666
|
+
(at2) => jobNamedAt(text2, name, at2, longer, workflowed.includes(at2))
|
|
12558
12667
|
);
|
|
12559
12668
|
}
|
|
12560
|
-
function jobNamedAt(
|
|
12561
|
-
const end =
|
|
12562
|
-
return (!
|
|
12563
|
-
(l) => boundedAt(
|
|
12669
|
+
function jobNamedAt(text2, name, at2, longer, withWorkflow) {
|
|
12670
|
+
const end = at2 + name.length;
|
|
12671
|
+
return (!text2.startsWith(" (", end) || withWorkflow && EVENT_SUFFIX.test(text2.slice(end))) && !longer.some(
|
|
12672
|
+
(l) => boundedAt(text2, l, JOB_NAME_CHAR).some((from) => from <= at2 && end <= from + l.length)
|
|
12564
12673
|
);
|
|
12565
12674
|
}
|
|
12566
12675
|
var longest = (names) => [...names].sort((a, b) => b.length - a.length)[0];
|
|
12567
|
-
function jobAt(
|
|
12676
|
+
function jobAt(text2, at2, jobNames) {
|
|
12568
12677
|
return longest(
|
|
12569
12678
|
jobNames.filter((name) => {
|
|
12570
12679
|
const longer = jobNames.filter((n) => n.length > name.length && n.includes(name));
|
|
12571
|
-
return boundedAt(
|
|
12680
|
+
return boundedAt(text2, name, JOB_NAME_CHAR).includes(at2) && jobNamedAt(text2, name, at2, longer, true);
|
|
12572
12681
|
})
|
|
12573
12682
|
);
|
|
12574
12683
|
}
|
|
12575
|
-
function workflowBefore(
|
|
12684
|
+
function workflowBefore(text2, slash, token, workflows) {
|
|
12576
12685
|
const whole = workflows.filter((w) => {
|
|
12577
12686
|
const from = slash - w.length;
|
|
12578
|
-
return w.length > 0 && from >= 0 &&
|
|
12687
|
+
return w.length > 0 && from >= 0 && text2.startsWith(w, from) && !PATH_CHAR.test(text2[from - 1] ?? " ");
|
|
12579
12688
|
});
|
|
12580
12689
|
return longest(whole) ?? token;
|
|
12581
12690
|
}
|
|
12582
|
-
function workflowJobs(
|
|
12691
|
+
function workflowJobs(text2, jobNames, workflows = []) {
|
|
12583
12692
|
const out = [];
|
|
12584
|
-
for (const m of
|
|
12585
|
-
const
|
|
12586
|
-
const job = jobAt(
|
|
12693
|
+
for (const m of text2.matchAll(WORKFLOW_JOB)) {
|
|
12694
|
+
const at2 = m.index + m[0].length;
|
|
12695
|
+
const job = jobAt(text2, at2, jobNames);
|
|
12587
12696
|
if (job === void 0) continue;
|
|
12588
|
-
out.push({ workflow: workflowBefore(
|
|
12697
|
+
out.push({ workflow: workflowBefore(text2, at2 - 3, m[1], workflows), job });
|
|
12589
12698
|
}
|
|
12590
12699
|
return out;
|
|
12591
12700
|
}
|
|
12592
12701
|
var PARENTHESIS = /^ \([^()]*\)/;
|
|
12593
|
-
function unheldSuffixJobs(
|
|
12702
|
+
function unheldSuffixJobs(text2, jobNames) {
|
|
12594
12703
|
const out = [];
|
|
12595
|
-
for (const
|
|
12596
|
-
if (jobAt(
|
|
12704
|
+
for (const at2 of afterWorkflows(text2)) {
|
|
12705
|
+
if (jobAt(text2, at2, jobNames) !== void 0) continue;
|
|
12597
12706
|
const job = longest(
|
|
12598
12707
|
jobNames.filter((name) => {
|
|
12599
|
-
const rest =
|
|
12600
|
-
return boundedAt(
|
|
12708
|
+
const rest = text2.slice(at2 + name.length);
|
|
12709
|
+
return boundedAt(text2, name, JOB_NAME_CHAR).includes(at2) && PARENTHESIS.test(rest) && !EVENT_SUFFIX.test(rest);
|
|
12601
12710
|
})
|
|
12602
12711
|
);
|
|
12603
12712
|
if (job !== void 0) out.push(job);
|
|
@@ -12606,8 +12715,8 @@ function unheldSuffixJobs(text, jobNames) {
|
|
|
12606
12715
|
}
|
|
12607
12716
|
function shaTokens(texts) {
|
|
12608
12717
|
const out = [];
|
|
12609
|
-
for (const
|
|
12610
|
-
for (const m of
|
|
12718
|
+
for (const text2 of texts) {
|
|
12719
|
+
for (const m of text2.matchAll(SHA2)) {
|
|
12611
12720
|
const token = m[0].toLowerCase();
|
|
12612
12721
|
if (token.length === 40 || /[0-9]/.test(token)) out.push(token);
|
|
12613
12722
|
}
|
|
@@ -12616,8 +12725,8 @@ function shaTokens(texts) {
|
|
|
12616
12725
|
}
|
|
12617
12726
|
function issueRefs(texts, repository) {
|
|
12618
12727
|
const out = [];
|
|
12619
|
-
for (const
|
|
12620
|
-
for (const m of
|
|
12728
|
+
for (const text2 of texts) {
|
|
12729
|
+
for (const m of text2.matchAll(ISSUE2)) out.push(qualifyIssue(m[1], repository));
|
|
12621
12730
|
}
|
|
12622
12731
|
return out;
|
|
12623
12732
|
}
|
|
@@ -12626,11 +12735,14 @@ function identifierMatches(claim2, items, repository) {
|
|
|
12626
12735
|
const shas = shaTokens(texts);
|
|
12627
12736
|
const issues = issueRefs(texts, repository);
|
|
12628
12737
|
const named = (value, inside) => texts.some((t) => t === value || occursBounded(t, value, inside));
|
|
12629
|
-
const
|
|
12738
|
+
const classnameOf = (item) => item.fields.find(([f]) => f === "classname")?.[1];
|
|
12739
|
+
const qualified = (item) => {
|
|
12740
|
+
const classname = classnameOf(item);
|
|
12741
|
+
return item.type === "test" && classname !== void 0 && named(`${classname}.${item.fields[0]?.[1]}`, WORD);
|
|
12742
|
+
};
|
|
12630
12743
|
const qualifiedNames = new Set(
|
|
12631
12744
|
items.filter(qualified).map((item) => item.fields[0]?.[1])
|
|
12632
12745
|
);
|
|
12633
|
-
const classnameOf = (item) => item.fields.find(([f]) => f === "classname")?.[1];
|
|
12634
12746
|
const classnames = [
|
|
12635
12747
|
...new Set(items.filter((item) => item.type === "test").map(classnameOf))
|
|
12636
12748
|
].filter((c) => c !== void 0);
|
|
@@ -12691,7 +12803,7 @@ function identifierMatches(claim2, items, repository) {
|
|
|
12691
12803
|
|
|
12692
12804
|
// src/retrieval/items.ts
|
|
12693
12805
|
function testId(test) {
|
|
12694
|
-
return test.classname
|
|
12806
|
+
return JSON.stringify([test.classname ?? null, test.name]);
|
|
12695
12807
|
}
|
|
12696
12808
|
function recordItems(evidence) {
|
|
12697
12809
|
const items = [];
|
|
@@ -12786,8 +12898,8 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
12786
12898
|
function stem(token) {
|
|
12787
12899
|
return token.length > 3 && token.endsWith("s") && !token.endsWith("ss") ? token.slice(0, -1) : token;
|
|
12788
12900
|
}
|
|
12789
|
-
function tokenize(
|
|
12790
|
-
return
|
|
12901
|
+
function tokenize(text2) {
|
|
12902
|
+
return text2.replace(new RegExp("([\\p{Ll}\\p{N}])(\\p{Lu})", "gu"), "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length > 1 && !STOPWORDS.has(t)).map(stem);
|
|
12791
12903
|
}
|
|
12792
12904
|
var Bm25Index = class {
|
|
12793
12905
|
termFrequencies;
|
|
@@ -12842,9 +12954,9 @@ function roundScore(score) {
|
|
|
12842
12954
|
function bestField(item, terms, index) {
|
|
12843
12955
|
let best = item.fields[0]?.[0] ?? "id";
|
|
12844
12956
|
let bestWeight = -1;
|
|
12845
|
-
for (const [name,
|
|
12957
|
+
for (const [name, text2] of item.fields) {
|
|
12846
12958
|
let weight = 0;
|
|
12847
|
-
for (const t of new Set(tokenize(
|
|
12959
|
+
for (const t of new Set(tokenize(text2))) if (terms.has(t)) weight += index.idf(t);
|
|
12848
12960
|
if (weight > bestWeight) {
|
|
12849
12961
|
best = name;
|
|
12850
12962
|
bestWeight = weight;
|
|
@@ -12853,7 +12965,7 @@ function bestField(item, terms, index) {
|
|
|
12853
12965
|
return best;
|
|
12854
12966
|
}
|
|
12855
12967
|
function itemTokens(item) {
|
|
12856
|
-
return item.fields.flatMap(([,
|
|
12968
|
+
return item.fields.flatMap(([, text2]) => tokenize(text2));
|
|
12857
12969
|
}
|
|
12858
12970
|
function compareRanked(a, b) {
|
|
12859
12971
|
const sa = a.candidate.score;
|
|
@@ -12939,7 +13051,11 @@ function sectionReason(type, evidence) {
|
|
|
12939
13051
|
}
|
|
12940
13052
|
function lookup(type, evidence) {
|
|
12941
13053
|
const entries = /* @__PURE__ */ new Map();
|
|
12942
|
-
const
|
|
13054
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
13055
|
+
const put = (id, value) => {
|
|
13056
|
+
if (entries.has(id)) duplicates.add(id);
|
|
13057
|
+
else entries.set(id, value);
|
|
13058
|
+
};
|
|
12943
13059
|
const items = evidence.items;
|
|
12944
13060
|
if (type === "file" && evidence.files?.status === "ok") {
|
|
12945
13061
|
for (const f of evidence.files.entries) put(f.path, f);
|
|
@@ -12953,7 +13069,7 @@ function lookup(type, evidence) {
|
|
|
12953
13069
|
} else if (type === "test" && items?.tests.status === "ok") {
|
|
12954
13070
|
for (const t of items.tests.entries) put(testId(t), t);
|
|
12955
13071
|
}
|
|
12956
|
-
return entries;
|
|
13072
|
+
return { entries, duplicates };
|
|
12957
13073
|
}
|
|
12958
13074
|
var ISSUE_REF2 = /^(?:[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100})?#[1-9][0-9]{0,9}$/;
|
|
12959
13075
|
function combinedCandidates(typed, floor) {
|
|
@@ -12973,17 +13089,17 @@ function ambiguousCandidates(combined, outcome) {
|
|
|
12973
13089
|
const tied = combined.filter((_, i) => scores[i] === top);
|
|
12974
13090
|
return tied.length >= 2 && new Set(tied.map(outcome)).size > 1;
|
|
12975
13091
|
}
|
|
12976
|
-
function namedWorkflowRuns(
|
|
13092
|
+
function namedWorkflowRuns(text2, combined, evidence) {
|
|
12977
13093
|
const section = evidence.checkRuns;
|
|
12978
13094
|
if (section?.status !== "ok") return [...combined];
|
|
12979
13095
|
const jobNames = [...new Set(section.runs.map((r) => r.name))];
|
|
12980
|
-
if (unheldSuffixJobs(
|
|
13096
|
+
if (unheldSuffixJobs(text2, jobNames).length > 0) return { reason: "no_matching_record_item" };
|
|
12981
13097
|
const recordedWorkflows = [
|
|
12982
13098
|
...new Set(
|
|
12983
13099
|
section.runs.flatMap((r) => [r.workflowPath, r.workflowName]).filter((w) => w !== void 0)
|
|
12984
13100
|
)
|
|
12985
13101
|
];
|
|
12986
|
-
const named = workflowJobs(
|
|
13102
|
+
const named = workflowJobs(text2, jobNames, recordedWorkflows);
|
|
12987
13103
|
if (named.length === 0) return [...combined];
|
|
12988
13104
|
const counted = new Map(
|
|
12989
13105
|
countedCheckRuns(section.runs, section.excludedIds).map((r) => [String(r.id), r])
|
|
@@ -13031,13 +13147,13 @@ function checkReaderClaim(claim2, evidence, repository) {
|
|
|
13031
13147
|
if (claim2.kind === "reference_closes") {
|
|
13032
13148
|
if (!ISSUE_REF2.test(declared)) return unverifiable2("no_comparable_record_field");
|
|
13033
13149
|
const head = evidence.pullRequest.headSha;
|
|
13034
|
-
const
|
|
13035
|
-
if (
|
|
13036
|
-
return verdictOf(
|
|
13150
|
+
const row2 = checkReferences(closesBlock(declared, head), evidence, repository)[0];
|
|
13151
|
+
if (row2 === void 0) return unverifiable2("no_comparable_record_field");
|
|
13152
|
+
return verdictOf(row2.verdict, row2.observed ?? null, row2.reason);
|
|
13037
13153
|
}
|
|
13038
13154
|
const missing = sectionReason(type, evidence);
|
|
13039
13155
|
if (missing !== void 0) return unverifiable2(missing);
|
|
13040
|
-
const entries = lookup(type, evidence);
|
|
13156
|
+
const { entries, duplicates } = lookup(type, evidence);
|
|
13041
13157
|
const typed = claim2.candidates.filter((c) => c.type === type);
|
|
13042
13158
|
if (typed.some((c) => entries.get(c.id) === void 0)) {
|
|
13043
13159
|
return unverifiable2("candidate_not_in_evidence");
|
|
@@ -13047,6 +13163,7 @@ function checkReaderClaim(claim2, evidence, repository) {
|
|
|
13047
13163
|
if (relevant.length === 0) {
|
|
13048
13164
|
return unverifiable2(truncated ? "file_list_truncated" : "no_matching_record_item");
|
|
13049
13165
|
}
|
|
13166
|
+
if (relevant.some((c) => duplicates.has(c.id))) return unverifiable2("duplicate_record_item");
|
|
13050
13167
|
if (type === "check_run") {
|
|
13051
13168
|
const scoped = namedWorkflowRuns(claim2.text, relevant, evidence);
|
|
13052
13169
|
if (!Array.isArray(scoped)) return unverifiable2(scoped.reason);
|
|
@@ -13261,10 +13378,10 @@ async function checkAt(input2, pr) {
|
|
|
13261
13378
|
const repository = pr.repository;
|
|
13262
13379
|
const ownRuns = await input2.afterPullRequest?.(pr) ?? [];
|
|
13263
13380
|
const report = await input2.report(pr);
|
|
13264
|
-
const
|
|
13265
|
-
const block = input2.block ?? recordBlock(extractHandbackBlock(
|
|
13381
|
+
const text2 = new TextDecoder("utf-8").decode(report.bytes);
|
|
13382
|
+
const block = input2.block ?? recordBlock(extractHandbackBlock(text2));
|
|
13266
13383
|
const found = block.status === "found" ? block.value : null;
|
|
13267
|
-
const proposed = input2.advisory === true ? extractClaims(
|
|
13384
|
+
const proposed = input2.advisory === true ? extractClaims(text2) : void 0;
|
|
13268
13385
|
const evidence = await input2.readers.evidence({
|
|
13269
13386
|
repository,
|
|
13270
13387
|
pullRequest: pr,
|
|
@@ -13360,6 +13477,102 @@ function noteText(note) {
|
|
|
13360
13477
|
return `${POSSIBLE_DISAGREEMENT}: ${shows}`;
|
|
13361
13478
|
}
|
|
13362
13479
|
|
|
13480
|
+
// src/remedy/remedies.ts
|
|
13481
|
+
var NO_REMEDY = "no remedy: the record decides";
|
|
13482
|
+
var RECIPE = "docs/ci-test-records.md";
|
|
13483
|
+
function line(text2) {
|
|
13484
|
+
return () => text2;
|
|
13485
|
+
}
|
|
13486
|
+
var decided = line(NO_REMEDY);
|
|
13487
|
+
var REMEDIES = {
|
|
13488
|
+
// The block (spec section 5). No claim row carries these; the survey counts them per pull request.
|
|
13489
|
+
block_missing: line(
|
|
13490
|
+
"The report has no dunstan-handback block: declare the claims in one (dunstan template writes it)."
|
|
13491
|
+
),
|
|
13492
|
+
block_ambiguous: line("The report has more than one dunstan-handback block: keep exactly one."),
|
|
13493
|
+
block_invalid: line(
|
|
13494
|
+
"The block failed validation: check it against spec/schema/handback-block-0.1.schema.json before handing back."
|
|
13495
|
+
),
|
|
13496
|
+
// Unverifiable, a tests row: the record holds no count to compare, or could not read it. These are
|
|
13497
|
+
// TESTS_REMEDY_REASONS, the lines the claims table prints under a tests row.
|
|
13498
|
+
record_not_found: line(
|
|
13499
|
+
`no run of the named workflow at the head uploaded that artifact from that job. Publish the JUnit record from CI (${RECIPE}), then check that the record's workflow, job, artifact and path are the ones the run used: dunstan template --tests-ci fills them.`
|
|
13500
|
+
),
|
|
13501
|
+
no_comparable_record_field: line(
|
|
13502
|
+
`this record's kind has nothing Dunstan can compare. Name a junit record a CI run uploaded (${RECIPE}); for a run that left no record in CI, declare {"kind": "local"}.`
|
|
13503
|
+
),
|
|
13504
|
+
local_run_not_in_record: line(
|
|
13505
|
+
`these counts are from a local run. Publish the JUnit record from CI (${RECIPE}), and run dunstan template --tests-ci after CI has finished on your last push.`
|
|
13506
|
+
),
|
|
13507
|
+
"source_unreadable:artifact": ({ hasToken }) => hasToken ? "the run uploaded the record, but it could not be read: it expired, was uploaded outside the latest attempt of the named job, the job had not completed, or the file is not well-formed JUnit. Re-run the job to upload it again, or check again once the job completes." : "GitHub answers an artifact download only with a token (401 without one, even on a public repository). Check again with GITHUB_TOKEN set to a token that can read the repository, with actions: read.",
|
|
13508
|
+
// Unverifiable, any other row: the record could not settle the claim.
|
|
13509
|
+
record_ambiguous: line("Several runs at the head disagree on the count."),
|
|
13510
|
+
checks_incomplete: line("CI had not finished when the record was read: check again once it has."),
|
|
13511
|
+
no_check_runs: line("There were no check runs at the head: declare checks only where CI runs."),
|
|
13512
|
+
no_succeeded_check_runs: line(
|
|
13513
|
+
"No check run at the head succeeded and none failed: nothing ran to settle the claim."
|
|
13514
|
+
),
|
|
13515
|
+
check_runs_not_at_head: line(
|
|
13516
|
+
"The check runs were read at another commit: declare the pull request head as headCommit."
|
|
13517
|
+
),
|
|
13518
|
+
file_list_truncated: line(
|
|
13519
|
+
"GitHub lists at most 3,000 changed files: split the change, or declare no more than it lists."
|
|
13520
|
+
),
|
|
13521
|
+
closing_link_unsettled: line(
|
|
13522
|
+
"GitHub had not yet linked the issue to the open pull request: check again once it has."
|
|
13523
|
+
),
|
|
13524
|
+
closing_base_not_default: line(
|
|
13525
|
+
"The pull request merged into a branch other than the default: the issue may close on a later merge."
|
|
13526
|
+
),
|
|
13527
|
+
closing_event_unsettled: line(
|
|
13528
|
+
"The issue is closed but its timeline does not yet show who closed it: check again later."
|
|
13529
|
+
),
|
|
13530
|
+
"source_unreadable:*": line("The source did not answer: rerun later or with a token."),
|
|
13531
|
+
"evidence_field_unpopulated:*": line("GitHub answered without the field the claim needs."),
|
|
13532
|
+
// Fail: the record decided the claim; nothing in the record is missing.
|
|
13533
|
+
head_mismatch: decided,
|
|
13534
|
+
declared_not_changed: decided,
|
|
13535
|
+
undeclared_file: decided,
|
|
13536
|
+
not_closing: decided,
|
|
13537
|
+
not_found: decided,
|
|
13538
|
+
not_reachable: decided,
|
|
13539
|
+
count_mismatch: decided,
|
|
13540
|
+
all_succeeded_mismatch: decided,
|
|
13541
|
+
none_failed_mismatch: decided,
|
|
13542
|
+
not_merged: decided,
|
|
13543
|
+
premature: decided,
|
|
13544
|
+
no_deployment: decided,
|
|
13545
|
+
// The survey's own reasons for a pull request it has no record of (docs/survey.md).
|
|
13546
|
+
pull_request_unreadable: line("The pull request could not be read: rerun later or with a token."),
|
|
13547
|
+
pull_request_moved: line(
|
|
13548
|
+
"The pull request moved twice while it was checked: rerun once it holds still."
|
|
13549
|
+
),
|
|
13550
|
+
rate_limited: line(
|
|
13551
|
+
"GitHub rate-limited the reads: rerun later, or with a token for a higher limit."
|
|
13552
|
+
),
|
|
13553
|
+
report_unavailable: line(
|
|
13554
|
+
"The named report could not be read: the author has no comment on the pull request, or the comments could not be listed."
|
|
13555
|
+
)
|
|
13556
|
+
};
|
|
13557
|
+
var TESTS_REMEDY_REASONS = [
|
|
13558
|
+
"record_not_found",
|
|
13559
|
+
"no_comparable_record_field",
|
|
13560
|
+
"local_run_not_in_record",
|
|
13561
|
+
"source_unreadable:artifact"
|
|
13562
|
+
];
|
|
13563
|
+
function remedyForReason(reason, context) {
|
|
13564
|
+
const exact = REMEDIES[reason];
|
|
13565
|
+
if (exact !== void 0) return exact(context);
|
|
13566
|
+
const colon = reason.indexOf(":");
|
|
13567
|
+
return colon === -1 ? void 0 : REMEDIES[`${reason.slice(0, colon)}:*`]?.(context);
|
|
13568
|
+
}
|
|
13569
|
+
function remedyFor(claim2, context) {
|
|
13570
|
+
if (claim2.verdict !== "unverifiable" || claim2.reason === void 0) return void 0;
|
|
13571
|
+
if (!TESTS_REMEDY_REASONS.includes(claim2.reason)) return void 0;
|
|
13572
|
+
if (!/^\/tests\/[0-9]+\/(?:count|failures)$/.test(claim2.field)) return void 0;
|
|
13573
|
+
return remedyForReason(claim2.reason, context);
|
|
13574
|
+
}
|
|
13575
|
+
|
|
13363
13576
|
// src/cli/output.ts
|
|
13364
13577
|
var EXIT = { pass: 0, fail: 1, unverifiable: 2, error: 3 };
|
|
13365
13578
|
function exitFor(verdict) {
|
|
@@ -13374,8 +13587,8 @@ var UsageError = class extends Error {
|
|
|
13374
13587
|
var WIDTH = 48;
|
|
13375
13588
|
function cell(value) {
|
|
13376
13589
|
if (value === void 0) return "";
|
|
13377
|
-
const
|
|
13378
|
-
return
|
|
13590
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value);
|
|
13591
|
+
return text2.length > WIDTH ? `${text2.slice(0, WIDTH - 1)}\u2026` : text2;
|
|
13379
13592
|
}
|
|
13380
13593
|
function table(rows) {
|
|
13381
13594
|
const widths = rows[0]?.map((_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length))) ?? [];
|
|
@@ -13383,7 +13596,7 @@ function table(rows) {
|
|
|
13383
13596
|
(r) => r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i] ?? 0)).join(" ").trimEnd()
|
|
13384
13597
|
).join("\n");
|
|
13385
13598
|
}
|
|
13386
|
-
function renderRecord(record2) {
|
|
13599
|
+
function renderRecord(record2, context) {
|
|
13387
13600
|
const p = record2.predicate;
|
|
13388
13601
|
const lines = [
|
|
13389
13602
|
`${p.checker.name} ${p.checker.version} ${p.subject.repository}#${p.subject.pullRequest} head ${p.subject.headSha}`,
|
|
@@ -13398,19 +13611,16 @@ function renderRecord(record2) {
|
|
|
13398
13611
|
lines.push(`block: invalid (${b.reason}: ${[...new Set(errors)].join("; ")})`);
|
|
13399
13612
|
} else lines.push(`block: missing (${b.reason})`);
|
|
13400
13613
|
if (p.claims.length > 0) {
|
|
13401
|
-
|
|
13402
|
-
"",
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
])
|
|
13412
|
-
])
|
|
13413
|
-
);
|
|
13614
|
+
const rows = table([
|
|
13615
|
+
["VERDICT", "CLAIM", "DECLARED", "OBSERVED", "REASON"],
|
|
13616
|
+
...p.claims.map((c) => [c.verdict, c.id, cell(c.declared), cell(c.observed), c.reason ?? ""])
|
|
13617
|
+
]).split("\n");
|
|
13618
|
+
lines.push("", rows[0]);
|
|
13619
|
+
p.claims.forEach((c, i) => {
|
|
13620
|
+
lines.push(rows[i + 1]);
|
|
13621
|
+
const remedy = remedyFor(c, context);
|
|
13622
|
+
if (remedy !== void 0) lines.push(` remedy: ${remedy}`);
|
|
13623
|
+
});
|
|
13414
13624
|
}
|
|
13415
13625
|
const unread2 = p.evidence.sources.filter((s) => s.error !== void 0);
|
|
13416
13626
|
if (unread2.length > 0) {
|
|
@@ -13529,7 +13739,7 @@ async function check(args, io) {
|
|
|
13529
13739
|
advisory: values.advisory === true
|
|
13530
13740
|
});
|
|
13531
13741
|
writeFileSync(out, serializeRecord(record2));
|
|
13532
|
-
io.out(renderRecord(record2));
|
|
13742
|
+
io.out(renderRecord(record2, { hasToken: client.hasToken }));
|
|
13533
13743
|
io.out(`record: ${out}
|
|
13534
13744
|
`);
|
|
13535
13745
|
if (assurance !== void 0) {
|
|
@@ -13539,6 +13749,95 @@ async function check(args, io) {
|
|
|
13539
13749
|
return exitFor(record2.predicate.verdict);
|
|
13540
13750
|
}
|
|
13541
13751
|
|
|
13752
|
+
// src/cli/junit-merge.ts
|
|
13753
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
13754
|
+
import { resolve as resolve2 } from "node:path";
|
|
13755
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
13756
|
+
|
|
13757
|
+
// src/junit/merge.ts
|
|
13758
|
+
var JunitMergeError = class extends Error {
|
|
13759
|
+
constructor(message) {
|
|
13760
|
+
super(message);
|
|
13761
|
+
this.name = "JunitMergeError";
|
|
13762
|
+
}
|
|
13763
|
+
};
|
|
13764
|
+
var HEADER = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
13765
|
+
function mergeJunit(inputs, count = (xml) => scanJunit(xml)) {
|
|
13766
|
+
if (inputs.length === 0) throw new JunitMergeError("no input: name one or more JUnit files");
|
|
13767
|
+
const sorted = [...inputs].sort((a, b) => compareCodeUnits(a.path, b.path));
|
|
13768
|
+
sorted.forEach((input2, i) => {
|
|
13769
|
+
if (i > 0 && sorted[i - 1]?.path === input2.path) {
|
|
13770
|
+
throw new JunitMergeError(`${input2.path} is given twice`);
|
|
13771
|
+
}
|
|
13772
|
+
});
|
|
13773
|
+
const sum = { executed: 0, failed: 0 };
|
|
13774
|
+
const parts = sorted.map((input2) => {
|
|
13775
|
+
let scan;
|
|
13776
|
+
try {
|
|
13777
|
+
scan = scanJunit(input2.xml);
|
|
13778
|
+
} catch (e) {
|
|
13779
|
+
if (e instanceof JunitError) throw new JunitMergeError(`${input2.path}: ${e.message}`);
|
|
13780
|
+
throw e;
|
|
13781
|
+
}
|
|
13782
|
+
sum.executed += scan.executed;
|
|
13783
|
+
sum.failed += scan.failed;
|
|
13784
|
+
const { root } = scan;
|
|
13785
|
+
return root.name === "testsuites" ? input2.xml.slice(root.contentStart, root.contentEnd) : input2.xml.slice(root.start, root.end);
|
|
13786
|
+
});
|
|
13787
|
+
const xml = [HEADER, "<testsuites>", ...parts, "</testsuites>", ""].join("\n");
|
|
13788
|
+
let merged;
|
|
13789
|
+
try {
|
|
13790
|
+
merged = count(xml);
|
|
13791
|
+
} catch (e) {
|
|
13792
|
+
if (e instanceof JunitError) throw new JunitMergeError(`the merged file: ${e.message}`);
|
|
13793
|
+
throw e;
|
|
13794
|
+
}
|
|
13795
|
+
if (merged.executed !== sum.executed || merged.failed !== sum.failed) {
|
|
13796
|
+
throw new JunitMergeError(
|
|
13797
|
+
`the merged file counts ${merged.executed} executed and ${merged.failed} failed; the inputs sum to ${sum.executed} and ${sum.failed}`
|
|
13798
|
+
);
|
|
13799
|
+
}
|
|
13800
|
+
return { xml, counts: sum };
|
|
13801
|
+
}
|
|
13802
|
+
|
|
13803
|
+
// src/cli/junit-merge.ts
|
|
13804
|
+
async function junitMerge(args, io) {
|
|
13805
|
+
const { values, positionals } = parseArgs2({
|
|
13806
|
+
args,
|
|
13807
|
+
strict: true,
|
|
13808
|
+
allowPositionals: true,
|
|
13809
|
+
options: { out: { type: "string" } }
|
|
13810
|
+
});
|
|
13811
|
+
if (values.out === void 0) throw new UsageError("--out <file> is required");
|
|
13812
|
+
const cwd = io.cwd ?? process.cwd();
|
|
13813
|
+
const out = resolve2(cwd, values.out);
|
|
13814
|
+
if (positionals.some((p) => resolve2(cwd, p) === out)) {
|
|
13815
|
+
throw new UsageError(`--out ${values.out} is one of the inputs`);
|
|
13816
|
+
}
|
|
13817
|
+
try {
|
|
13818
|
+
const merged = mergeJunit(
|
|
13819
|
+
positionals.map((path) => {
|
|
13820
|
+
try {
|
|
13821
|
+
return { path, xml: readFileSync4(resolve2(cwd, path), "utf8") };
|
|
13822
|
+
} catch (e) {
|
|
13823
|
+
throw new JunitMergeError(`${path}: ${e.message}`);
|
|
13824
|
+
}
|
|
13825
|
+
})
|
|
13826
|
+
);
|
|
13827
|
+
writeFileSync2(out, merged.xml);
|
|
13828
|
+
io.err(
|
|
13829
|
+
`dunstan: ${values.out}: ${positionals.length} ${positionals.length === 1 ? "file" : "files"}, ${merged.counts.executed} test cases executed, ${merged.counts.failed} failed
|
|
13830
|
+
`
|
|
13831
|
+
);
|
|
13832
|
+
return 0;
|
|
13833
|
+
} catch (e) {
|
|
13834
|
+
if (!(e instanceof JunitMergeError)) throw e;
|
|
13835
|
+
io.err(`dunstan: junit-merge refused, nothing written: ${e.message}
|
|
13836
|
+
`);
|
|
13837
|
+
return EXIT.error;
|
|
13838
|
+
}
|
|
13839
|
+
}
|
|
13840
|
+
|
|
13542
13841
|
// node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.0_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
13543
13842
|
import process2 from "node:process";
|
|
13544
13843
|
|
|
@@ -14376,14 +14675,14 @@ function promiseAllObject(promisesObj) {
|
|
|
14376
14675
|
}
|
|
14377
14676
|
function randomString(length = 10) {
|
|
14378
14677
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
14379
|
-
let
|
|
14678
|
+
let str3 = "";
|
|
14380
14679
|
for (let i = 0; i < length; i++) {
|
|
14381
|
-
|
|
14680
|
+
str3 += chars[Math.floor(Math.random() * chars.length)];
|
|
14382
14681
|
}
|
|
14383
|
-
return
|
|
14682
|
+
return str3;
|
|
14384
14683
|
}
|
|
14385
|
-
function esc(
|
|
14386
|
-
return JSON.stringify(
|
|
14684
|
+
function esc(str3) {
|
|
14685
|
+
return JSON.stringify(str3);
|
|
14387
14686
|
}
|
|
14388
14687
|
function slugify(input2) {
|
|
14389
14688
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -14497,8 +14796,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
|
|
|
14497
14796
|
"symbol",
|
|
14498
14797
|
"undefined"
|
|
14499
14798
|
]);
|
|
14500
|
-
function escapeRegex(
|
|
14501
|
-
return
|
|
14799
|
+
function escapeRegex(str3) {
|
|
14800
|
+
return str3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14502
14801
|
}
|
|
14503
14802
|
function clone(inst, def, params) {
|
|
14504
14803
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
@@ -14752,13 +15051,13 @@ function getSizableOrigin(input2) {
|
|
|
14752
15051
|
return "unknown";
|
|
14753
15052
|
}
|
|
14754
15053
|
var highSurrogate = /[\uD800-\uDBFF]/;
|
|
14755
|
-
function codePointLength(
|
|
14756
|
-
const units =
|
|
14757
|
-
if (!highSurrogate.test(
|
|
15054
|
+
function codePointLength(str3) {
|
|
15055
|
+
const units = str3.length;
|
|
15056
|
+
if (!highSurrogate.test(str3))
|
|
14758
15057
|
return units;
|
|
14759
15058
|
let count = units;
|
|
14760
15059
|
for (let i = 0; i < units - 1; i++) {
|
|
14761
|
-
if ((
|
|
15060
|
+
if ((str3.charCodeAt(i) & 64512) === 55296 && (str3.charCodeAt(i + 1) & 64512) === 56320) {
|
|
14762
15061
|
count--;
|
|
14763
15062
|
i++;
|
|
14764
15063
|
}
|
|
@@ -14851,9 +15150,9 @@ var Class = class {
|
|
|
14851
15150
|
constructor(..._args) {
|
|
14852
15151
|
}
|
|
14853
15152
|
};
|
|
14854
|
-
function members(proto,
|
|
14855
|
-
for (const key in
|
|
14856
|
-
const desc = Object.getOwnPropertyDescriptor(
|
|
15153
|
+
function members(proto, table3) {
|
|
15154
|
+
for (const key in table3) {
|
|
15155
|
+
const desc = Object.getOwnPropertyDescriptor(table3, key);
|
|
14857
15156
|
if (desc.get)
|
|
14858
15157
|
Object.defineProperty(proto, key, { ...desc, enumerable: false });
|
|
14859
15158
|
else
|
|
@@ -14868,10 +15167,10 @@ function hide(inst, key, value) {
|
|
|
14868
15167
|
return own(inst, key, value, false);
|
|
14869
15168
|
}
|
|
14870
15169
|
// @__NO_SIDE_EFFECTS__
|
|
14871
|
-
function derived(computes,
|
|
15170
|
+
function derived(computes, table3) {
|
|
14872
15171
|
for (const key in computes) {
|
|
14873
15172
|
const compute = computes[key];
|
|
14874
|
-
Object.defineProperty(
|
|
15173
|
+
Object.defineProperty(table3, key, {
|
|
14875
15174
|
configurable: true,
|
|
14876
15175
|
enumerable: true,
|
|
14877
15176
|
get() {
|
|
@@ -14882,7 +15181,7 @@ function derived(computes, table2) {
|
|
|
14882
15181
|
}
|
|
14883
15182
|
});
|
|
14884
15183
|
}
|
|
14885
|
-
return
|
|
15184
|
+
return table3;
|
|
14886
15185
|
}
|
|
14887
15186
|
function defineBound(proto, key, fn) {
|
|
14888
15187
|
Object.defineProperty(proto, key, {
|
|
@@ -16114,8 +16413,8 @@ var Doc = class {
|
|
|
16114
16413
|
const lines = content.split("\n").filter((x) => x);
|
|
16115
16414
|
const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));
|
|
16116
16415
|
const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
|
|
16117
|
-
for (const
|
|
16118
|
-
this.content.push(
|
|
16416
|
+
for (const line2 of dedented) {
|
|
16417
|
+
this.content.push(line2);
|
|
16119
16418
|
}
|
|
16120
16419
|
}
|
|
16121
16420
|
compile() {
|
|
@@ -18568,7 +18867,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
18568
18867
|
var NONE = 0;
|
|
18569
18868
|
var ASSUMED = 1;
|
|
18570
18869
|
var PROVEN = 2;
|
|
18571
|
-
function isRecursive(inst, stack,
|
|
18870
|
+
function isRecursive(inst, stack, resolve5) {
|
|
18572
18871
|
const cached2 = recursive.get(inst);
|
|
18573
18872
|
if (cached2 !== void 0)
|
|
18574
18873
|
return cached2 ? PROVEN : NONE;
|
|
@@ -18578,7 +18877,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
18578
18877
|
let result = NONE;
|
|
18579
18878
|
const check3 = (child) => {
|
|
18580
18879
|
if (result !== PROVEN && child?._zod) {
|
|
18581
|
-
const answer = isRecursive(child, stack,
|
|
18880
|
+
const answer = isRecursive(child, stack, resolve5);
|
|
18582
18881
|
if (answer > result)
|
|
18583
18882
|
result = answer;
|
|
18584
18883
|
}
|
|
@@ -18589,7 +18888,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
18589
18888
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
18590
18889
|
if (spread && !desc.enumerable)
|
|
18591
18890
|
continue;
|
|
18592
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
18891
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
|
|
18593
18892
|
if (child > answer)
|
|
18594
18893
|
answer = child;
|
|
18595
18894
|
}
|
|
@@ -18653,7 +18952,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
18653
18952
|
break;
|
|
18654
18953
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
18655
18954
|
case "lazy": {
|
|
18656
|
-
const inner = def._cachedInner ?? (
|
|
18955
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
18657
18956
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
18658
18957
|
break;
|
|
18659
18958
|
}
|
|
@@ -22851,8 +23150,8 @@ function ko_default() {
|
|
|
22851
23150
|
}
|
|
22852
23151
|
|
|
22853
23152
|
// node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/lt.js
|
|
22854
|
-
var capitalizeFirstCharacter = (
|
|
22855
|
-
return
|
|
23153
|
+
var capitalizeFirstCharacter = (text2) => {
|
|
23154
|
+
return text2.charAt(0).toUpperCase() + text2.slice(1);
|
|
22856
23155
|
};
|
|
22857
23156
|
function getUnitTypeFromNumber(number4) {
|
|
22858
23157
|
const abs = Math.abs(number4);
|
|
@@ -24212,8 +24511,8 @@ var error43 = () => {
|
|
|
24212
24511
|
case "not_multiple_of":
|
|
24213
24512
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
24214
24513
|
case "unrecognized_keys": {
|
|
24215
|
-
const
|
|
24216
|
-
return `Chave${
|
|
24514
|
+
const plural4 = issue2.keys.length > 1 ? "s" : "";
|
|
24515
|
+
return `Chave${plural4} inv\xE1lida${plural4}: ${joinValues(issue2.keys, ", ")}`;
|
|
24217
24516
|
}
|
|
24218
24517
|
case "invalid_key":
|
|
24219
24518
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -24357,8 +24656,8 @@ var error44 = () => {
|
|
|
24357
24656
|
case "not_multiple_of":
|
|
24358
24657
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
24359
24658
|
case "unrecognized_keys": {
|
|
24360
|
-
const
|
|
24361
|
-
return `Chave${
|
|
24659
|
+
const plural4 = issue2.keys.length > 1 ? "s" : "";
|
|
24660
|
+
return `Chave${plural4} inv\xE1lida${plural4}: ${joinValues(issue2.keys, ", ")}`;
|
|
24362
24661
|
}
|
|
24363
24662
|
case "invalid_key":
|
|
24364
24663
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -34763,16 +35062,16 @@ var ReadBuffer = class {
|
|
|
34763
35062
|
if (index === -1) {
|
|
34764
35063
|
return null;
|
|
34765
35064
|
}
|
|
34766
|
-
const
|
|
35065
|
+
const line2 = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
|
|
34767
35066
|
this._buffer = this._buffer.subarray(index + 1);
|
|
34768
|
-
return deserializeMessage(
|
|
35067
|
+
return deserializeMessage(line2);
|
|
34769
35068
|
}
|
|
34770
35069
|
clear() {
|
|
34771
35070
|
this._buffer = void 0;
|
|
34772
35071
|
}
|
|
34773
35072
|
};
|
|
34774
|
-
function deserializeMessage(
|
|
34775
|
-
return JSONRPCMessageSchema.parse(JSON.parse(
|
|
35073
|
+
function deserializeMessage(line2) {
|
|
35074
|
+
return JSONRPCMessageSchema.parse(JSON.parse(line2));
|
|
34776
35075
|
}
|
|
34777
35076
|
function serializeMessage(message) {
|
|
34778
35077
|
return JSON.stringify(message) + "\n";
|
|
@@ -34834,20 +35133,20 @@ var StdioServerTransport = class {
|
|
|
34834
35133
|
this.onclose?.();
|
|
34835
35134
|
}
|
|
34836
35135
|
send(message) {
|
|
34837
|
-
return new Promise((
|
|
35136
|
+
return new Promise((resolve5) => {
|
|
34838
35137
|
const json2 = serializeMessage(message);
|
|
34839
35138
|
if (this._stdout.write(json2)) {
|
|
34840
|
-
|
|
35139
|
+
resolve5();
|
|
34841
35140
|
} else {
|
|
34842
|
-
this._stdout.once("drain",
|
|
35141
|
+
this._stdout.once("drain", resolve5);
|
|
34843
35142
|
}
|
|
34844
35143
|
});
|
|
34845
35144
|
}
|
|
34846
35145
|
};
|
|
34847
35146
|
|
|
34848
35147
|
// src/mcp/server.ts
|
|
34849
|
-
import { existsSync, writeFileSync as
|
|
34850
|
-
import { basename as basename2, resolve as
|
|
35148
|
+
import { existsSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
35149
|
+
import { basename as basename2, resolve as resolve3 } from "node:path";
|
|
34851
35150
|
|
|
34852
35151
|
// node_modules/.pnpm/zod@4.6.5/node_modules/zod/v3/helpers/util.js
|
|
34853
35152
|
var util;
|
|
@@ -40823,7 +41122,7 @@ var Protocol = class {
|
|
|
40823
41122
|
return;
|
|
40824
41123
|
}
|
|
40825
41124
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
40826
|
-
await new Promise((
|
|
41125
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
|
|
40827
41126
|
options?.signal?.throwIfAborted();
|
|
40828
41127
|
}
|
|
40829
41128
|
} catch (error62) {
|
|
@@ -40840,7 +41139,7 @@ var Protocol = class {
|
|
|
40840
41139
|
*/
|
|
40841
41140
|
request(request, resultSchema, options) {
|
|
40842
41141
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
40843
|
-
return new Promise((
|
|
41142
|
+
return new Promise((resolve5, reject) => {
|
|
40844
41143
|
const earlyReject = (error62) => {
|
|
40845
41144
|
reject(error62);
|
|
40846
41145
|
};
|
|
@@ -40918,7 +41217,7 @@ var Protocol = class {
|
|
|
40918
41217
|
if (!parseResult.success) {
|
|
40919
41218
|
reject(parseResult.error);
|
|
40920
41219
|
} else {
|
|
40921
|
-
|
|
41220
|
+
resolve5(parseResult.data);
|
|
40922
41221
|
}
|
|
40923
41222
|
} catch (error62) {
|
|
40924
41223
|
reject(error62);
|
|
@@ -41172,20 +41471,20 @@ var Protocol = class {
|
|
|
41172
41471
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
41173
41472
|
*/
|
|
41174
41473
|
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
41175
|
-
let
|
|
41474
|
+
let interval2 = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
41176
41475
|
try {
|
|
41177
41476
|
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
41178
41477
|
if (task?.pollInterval) {
|
|
41179
|
-
|
|
41478
|
+
interval2 = task.pollInterval;
|
|
41180
41479
|
}
|
|
41181
41480
|
} catch {
|
|
41182
41481
|
}
|
|
41183
|
-
return new Promise((
|
|
41482
|
+
return new Promise((resolve5, reject) => {
|
|
41184
41483
|
if (signal.aborted) {
|
|
41185
41484
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
41186
41485
|
return;
|
|
41187
41486
|
}
|
|
41188
|
-
const timeoutId = setTimeout(
|
|
41487
|
+
const timeoutId = setTimeout(resolve5, interval2);
|
|
41189
41488
|
signal.addEventListener("abort", () => {
|
|
41190
41489
|
clearTimeout(timeoutId);
|
|
41191
41490
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -42316,7 +42615,7 @@ var McpServer = class {
|
|
|
42316
42615
|
let task = createTaskResult.task;
|
|
42317
42616
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
42318
42617
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
42319
|
-
await new Promise((
|
|
42618
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
|
|
42320
42619
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
42321
42620
|
if (!updatedTask) {
|
|
42322
42621
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -42936,13 +43235,13 @@ function sentenceAt(report, span) {
|
|
|
42936
43235
|
const lines = report.split("\n");
|
|
42937
43236
|
const starts = [];
|
|
42938
43237
|
let offset = 0;
|
|
42939
|
-
for (const
|
|
43238
|
+
for (const line2 of lines) {
|
|
42940
43239
|
starts.push(offset);
|
|
42941
|
-
offset +=
|
|
43240
|
+
offset += line2.length + 1;
|
|
42942
43241
|
}
|
|
42943
|
-
const lineOf = (
|
|
43242
|
+
const lineOf = (at2) => {
|
|
42944
43243
|
let i = 0;
|
|
42945
|
-
while (i + 1 < starts.length && starts[i + 1] <=
|
|
43244
|
+
while (i + 1 < starts.length && starts[i + 1] <= at2) i++;
|
|
42946
43245
|
return i;
|
|
42947
43246
|
};
|
|
42948
43247
|
const blank = (i) => (lines[i] ?? "").trim() === "";
|
|
@@ -42965,8 +43264,8 @@ function sentenceAt(report, span) {
|
|
|
42965
43264
|
break;
|
|
42966
43265
|
}
|
|
42967
43266
|
}
|
|
42968
|
-
const
|
|
42969
|
-
return
|
|
43267
|
+
const text2 = unit.slice(start, end).replace(/^\s*(?:[-*+]|\d{1,3}[.)]|#{1,6}|>)\s+/, "").replace(/\s+/g, " ").trim();
|
|
43268
|
+
return text2.length > 300 ? `${text2.slice(0, 299)}\u2026` : text2;
|
|
42970
43269
|
}
|
|
42971
43270
|
function suggestDeclarations(report) {
|
|
42972
43271
|
const block = extractHandbackBlock(report);
|
|
@@ -43045,19 +43344,19 @@ var INSTRUCTIONS = `Before you say a task is done: generate your dunstan-handbac
|
|
|
43045
43344
|
var SUGGEST_DESCRIPTION = `List the sentences in your report's prose that look like claims your dunstan-handback block does not declare, before you say done. Give report, the report text. For each, declare it in the block, or reword it if it is not a claim about this PR. Offline: needs no pull request, reads nothing and writes nothing. Never a verdict: nothing passes or fails, and nothing is written to a record. The same text as \`dunstan suggest\`.`;
|
|
43046
43345
|
var DESCRIPTION = `Check a completion handback against the repository's own record, before you say done. Give the subject (repository owner/name and pull request number) and exactly one of: block, the dunstan-handback block as a JSON object; or report, the report text that carries exactly one \`\`\`dunstan-handback fence. Of the checks claims, checks.allSucceeded is true only if every counted check run concluded success, so a skipped or neutral run makes it false; checks.noneFailed is true if none concluded other than success, skipped or neutral and at least one succeeded. Returns the verdict (pass, fail or unverifiable), the claims table, and the full record as JSON. Same checker and same verdict as \`dunstan check\` on the same evidence; no model decides. Reads GitHub metadata with GITHUB_TOKEN and writes nothing to GitHub or the repository; the record is written to a file only when you pass outPath. If the verdict is not pass, report the rows as they are; change the work, not the block.`;
|
|
43047
43346
|
function reportOfBlock(block) {
|
|
43048
|
-
const
|
|
43347
|
+
const text2 = canonicalize(block);
|
|
43049
43348
|
return {
|
|
43050
43349
|
report: {
|
|
43051
|
-
bytes: new TextEncoder().encode(
|
|
43350
|
+
bytes: new TextEncoder().encode(text2),
|
|
43052
43351
|
source: { kind: "api", locator: `mcp ${TOOL_NAME}#block` }
|
|
43053
43352
|
},
|
|
43054
|
-
block: recordBlock(readBlockContent(
|
|
43353
|
+
block: recordBlock(readBlockContent(text2))
|
|
43055
43354
|
};
|
|
43056
43355
|
}
|
|
43057
|
-
function reportOfText(
|
|
43356
|
+
function reportOfText(text2) {
|
|
43058
43357
|
return {
|
|
43059
43358
|
report: {
|
|
43060
|
-
bytes: new TextEncoder().encode(
|
|
43359
|
+
bytes: new TextEncoder().encode(text2),
|
|
43061
43360
|
source: { kind: "api", locator: `mcp ${TOOL_NAME}#report` }
|
|
43062
43361
|
}
|
|
43063
43362
|
};
|
|
@@ -43068,7 +43367,7 @@ async function checkHandback(input2, options) {
|
|
|
43068
43367
|
"pass exactly one of block (the handback block as a JSON object) or report (text carrying one dunstan-handback fence)"
|
|
43069
43368
|
);
|
|
43070
43369
|
}
|
|
43071
|
-
const outPath = input2.outPath === void 0 ? void 0 :
|
|
43370
|
+
const outPath = input2.outPath === void 0 ? void 0 : resolve3(input2.outPath);
|
|
43072
43371
|
if (outPath !== void 0 && existsSync(outPath)) {
|
|
43073
43372
|
throw new InputError(`${outPath} exists; the tool never overwrites a file`);
|
|
43074
43373
|
}
|
|
@@ -43100,10 +43399,10 @@ async function checkHandback(input2, options) {
|
|
|
43100
43399
|
recordFile: basename2(recordFile)
|
|
43101
43400
|
});
|
|
43102
43401
|
const serialized = serializeRecord(record2);
|
|
43103
|
-
if (outPath !== void 0)
|
|
43104
|
-
return handbackResult(record2, outPath, notes);
|
|
43402
|
+
if (outPath !== void 0) writeFileSync3(outPath, serialized, { flag: "wx" });
|
|
43403
|
+
return handbackResult(record2, outPath, notes, { hasToken: client.hasToken });
|
|
43105
43404
|
}
|
|
43106
|
-
function handbackResult(record2, outPath, notes) {
|
|
43405
|
+
function handbackResult(record2, outPath, notes, context) {
|
|
43107
43406
|
const verdict = record2.predicate.verdict;
|
|
43108
43407
|
const head = [`verdict: ${verdict}`];
|
|
43109
43408
|
if (verdict !== "pass") {
|
|
@@ -43117,7 +43416,7 @@ function handbackResult(record2, outPath, notes) {
|
|
|
43117
43416
|
content: [
|
|
43118
43417
|
{ type: "text", text: `${head.join("\n")}
|
|
43119
43418
|
` },
|
|
43120
|
-
{ type: "text", text: renderRecord(record2) },
|
|
43419
|
+
{ type: "text", text: renderRecord(record2, context) },
|
|
43121
43420
|
{ type: "text", text: serializeRecord(record2) }
|
|
43122
43421
|
]
|
|
43123
43422
|
};
|
|
@@ -43203,8 +43502,8 @@ async function mcp(args, io) {
|
|
|
43203
43502
|
warn: io.err
|
|
43204
43503
|
});
|
|
43205
43504
|
const stdin = io.stdin ?? process.stdin;
|
|
43206
|
-
const closed = new Promise((
|
|
43207
|
-
server.server.onclose =
|
|
43505
|
+
const closed = new Promise((resolve5) => {
|
|
43506
|
+
server.server.onclose = resolve5;
|
|
43208
43507
|
});
|
|
43209
43508
|
stdin.once("end", () => void server.close());
|
|
43210
43509
|
await server.connect(new StdioServerTransport(stdin, io.stdout ?? process.stdout));
|
|
@@ -43213,9 +43512,9 @@ async function mcp(args, io) {
|
|
|
43213
43512
|
}
|
|
43214
43513
|
|
|
43215
43514
|
// src/cli/rerun.ts
|
|
43216
|
-
import { existsSync as existsSync2, readFileSync as
|
|
43217
|
-
import { basename as basename3, resolve as
|
|
43218
|
-
import { parseArgs as
|
|
43515
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
43516
|
+
import { basename as basename3, resolve as resolve4 } from "node:path";
|
|
43517
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
43219
43518
|
function diffPointers(a, b, pointer = "") {
|
|
43220
43519
|
const isObject2 = (v) => v !== null && typeof v === "object";
|
|
43221
43520
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
@@ -43237,7 +43536,7 @@ function defaultOut(path) {
|
|
|
43237
43536
|
return path.endsWith(".json") ? `${path.slice(0, -5)}.rerun.json` : `${path}.rerun.json`;
|
|
43238
43537
|
}
|
|
43239
43538
|
async function rerun(args, io) {
|
|
43240
|
-
const { values, positionals } =
|
|
43539
|
+
const { values, positionals } = parseArgs3({
|
|
43241
43540
|
args,
|
|
43242
43541
|
strict: true,
|
|
43243
43542
|
allowPositionals: true,
|
|
@@ -43246,9 +43545,9 @@ async function rerun(args, io) {
|
|
|
43246
43545
|
if (positionals.length !== 1) throw new UsageError("dunstan rerun <record.json> [--out <path>]");
|
|
43247
43546
|
const path = positionals[0];
|
|
43248
43547
|
const out = values.out ?? defaultOut(path);
|
|
43249
|
-
if (
|
|
43548
|
+
if (resolve4(out) === resolve4(path))
|
|
43250
43549
|
throw new UsageError("rerun never overwrites the record it re-runs");
|
|
43251
|
-
const value = parseStrictJson(
|
|
43550
|
+
const value = parseStrictJson(readFileSync5(path, "utf8"));
|
|
43252
43551
|
const errors = validateRecord(value);
|
|
43253
43552
|
if (errors.length > 0) {
|
|
43254
43553
|
throw new UsageError(
|
|
@@ -43286,7 +43585,7 @@ async function rerun(args, io) {
|
|
|
43286
43585
|
});
|
|
43287
43586
|
if (existsSync2(out)) io.err(`dunstan: replacing ${out}
|
|
43288
43587
|
`);
|
|
43289
|
-
|
|
43588
|
+
writeFileSync4(out, serializeRecord(record2));
|
|
43290
43589
|
const p = record2.predicate;
|
|
43291
43590
|
if (p.digests.evidence !== old.digests.evidence) {
|
|
43292
43591
|
const fields = diffPointers(
|
|
@@ -43305,17 +43604,17 @@ async function rerun(args, io) {
|
|
|
43305
43604
|
|
|
43306
43605
|
`);
|
|
43307
43606
|
}
|
|
43308
|
-
io.out(renderRecord(record2));
|
|
43607
|
+
io.out(renderRecord(record2, { hasToken: client.hasToken }));
|
|
43309
43608
|
io.out(`record: ${out} (the original ${path} is unchanged)
|
|
43310
43609
|
`);
|
|
43311
43610
|
return exitFor(p.verdict);
|
|
43312
43611
|
}
|
|
43313
43612
|
|
|
43314
43613
|
// src/cli/suggest.ts
|
|
43315
|
-
import { readFileSync as
|
|
43316
|
-
import { parseArgs as
|
|
43614
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
43615
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
43317
43616
|
async function suggest(args, io) {
|
|
43318
|
-
const { values } =
|
|
43617
|
+
const { values } = parseArgs4({
|
|
43319
43618
|
args,
|
|
43320
43619
|
strict: true,
|
|
43321
43620
|
allowPositionals: false,
|
|
@@ -43324,7 +43623,7 @@ async function suggest(args, io) {
|
|
|
43324
43623
|
const file2 = values["report-file"];
|
|
43325
43624
|
let bytes;
|
|
43326
43625
|
try {
|
|
43327
|
-
bytes = file2 === void 0 || file2 === "-" ? io.readStdin() :
|
|
43626
|
+
bytes = file2 === void 0 || file2 === "-" ? io.readStdin() : readFileSync6(file2);
|
|
43328
43627
|
} catch (e) {
|
|
43329
43628
|
io.err(`dunstan: cannot read the report ${file2 ?? "stdin"}: ${e.message}
|
|
43330
43629
|
`);
|
|
@@ -43334,61 +43633,815 @@ async function suggest(args, io) {
|
|
|
43334
43633
|
return 0;
|
|
43335
43634
|
}
|
|
43336
43635
|
|
|
43636
|
+
// src/cli/survey.ts
|
|
43637
|
+
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
43638
|
+
import { basename as basename4 } from "node:path";
|
|
43639
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
43640
|
+
|
|
43641
|
+
// src/survey/mapping.ts
|
|
43642
|
+
var SURVEY_MAPPING_VERSION = "0.2";
|
|
43643
|
+
var CLAIM_TYPES = [
|
|
43644
|
+
{ type: "T1", label: "head commit" },
|
|
43645
|
+
{ type: "T2", label: "files changed" },
|
|
43646
|
+
{ type: "T3", label: "test count" },
|
|
43647
|
+
{ type: "T4", label: "test failures" },
|
|
43648
|
+
{ type: "T5", label: "check run total" },
|
|
43649
|
+
{ type: "T6", label: "checks succeeded / none failed" },
|
|
43650
|
+
{ type: "T7", label: "merged at" },
|
|
43651
|
+
{ type: "T8", label: "deployed at" },
|
|
43652
|
+
{ type: "R", label: "references (a row the original study did not have)" }
|
|
43653
|
+
];
|
|
43654
|
+
var NOT_RECORD_CHECKABLE = [
|
|
43655
|
+
"record_not_found",
|
|
43656
|
+
"no_comparable_record_field",
|
|
43657
|
+
"local_run_not_in_record"
|
|
43658
|
+
];
|
|
43659
|
+
function claimType(claim2) {
|
|
43660
|
+
const { check: check3, field } = claim2;
|
|
43661
|
+
if (check3 === "head") return "T1";
|
|
43662
|
+
if (check3 === "scope") return "T2";
|
|
43663
|
+
if (check3 === "reference") return "R";
|
|
43664
|
+
if (check3 === "count") {
|
|
43665
|
+
if (/^\/tests\/[0-9]+\/count$/.test(field)) return "T3";
|
|
43666
|
+
if (/^\/tests\/[0-9]+\/failures$/.test(field)) return "T4";
|
|
43667
|
+
if (field === "/checks/total") return "T5";
|
|
43668
|
+
if (field === "/checks/allSucceeded" || field === "/checks/noneFailed") return "T6";
|
|
43669
|
+
}
|
|
43670
|
+
if (check3 === "time") {
|
|
43671
|
+
if (field === "/mergedAt") return "T7";
|
|
43672
|
+
if (field === "/deployedAt") return "T8";
|
|
43673
|
+
}
|
|
43674
|
+
throw new Error(`no claim type for ${check3} ${field}`);
|
|
43675
|
+
}
|
|
43676
|
+
function column(claim2) {
|
|
43677
|
+
if (claim2.verdict === "pass") return "supported";
|
|
43678
|
+
if (claim2.verdict === "fail") return "contradicted";
|
|
43679
|
+
return NOT_RECORD_CHECKABLE.includes(claim2.reason ?? "") ? "notRecordCheckable" : "unverifiable";
|
|
43680
|
+
}
|
|
43681
|
+
|
|
43682
|
+
// src/survey/aggregate.ts
|
|
43683
|
+
var Z = 1.96;
|
|
43684
|
+
var DECIMALS = 4;
|
|
43685
|
+
function fixed(x) {
|
|
43686
|
+
return Math.min(1, Math.max(0, x)).toFixed(DECIMALS);
|
|
43687
|
+
}
|
|
43688
|
+
function rate(numerator, denominator) {
|
|
43689
|
+
if (denominator === 0) return { numerator, denominator, value: null, wilson95: null };
|
|
43690
|
+
const n = denominator;
|
|
43691
|
+
const p = numerator / n;
|
|
43692
|
+
const z2 = Z * Z;
|
|
43693
|
+
const centre = (p + z2 / (2 * n)) / (1 + z2 / n);
|
|
43694
|
+
const half = Z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n)) / (1 + z2 / n);
|
|
43695
|
+
return {
|
|
43696
|
+
numerator,
|
|
43697
|
+
denominator,
|
|
43698
|
+
value: fixed(p),
|
|
43699
|
+
wilson95: {
|
|
43700
|
+
low: numerator === 0 ? fixed(0) : fixed(centre - half),
|
|
43701
|
+
high: numerator === n ? fixed(1) : fixed(centre + half)
|
|
43702
|
+
}
|
|
43703
|
+
};
|
|
43704
|
+
}
|
|
43705
|
+
var zero = () => ({
|
|
43706
|
+
supported: 0,
|
|
43707
|
+
unverifiable: 0,
|
|
43708
|
+
notRecordCheckable: 0,
|
|
43709
|
+
contradicted: 0
|
|
43710
|
+
});
|
|
43711
|
+
var byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
43712
|
+
function aggregate(entries, listed2, advisory, reader) {
|
|
43713
|
+
const context = { hasToken: reader.authenticated };
|
|
43714
|
+
const totals = zero();
|
|
43715
|
+
const types = new Map(CLAIM_TYPES.map((t) => [t.type, zero()]));
|
|
43716
|
+
const unsettled2 = /* @__PURE__ */ new Map();
|
|
43717
|
+
const noRecord = /* @__PURE__ */ new Map();
|
|
43718
|
+
const notes = /* @__PURE__ */ new Map();
|
|
43719
|
+
const blocks = { found: 0, missing: 0, invalid: 0, ambiguous: 0 };
|
|
43720
|
+
let withRecord = 0;
|
|
43721
|
+
let advised = 0;
|
|
43722
|
+
for (const entry of entries) {
|
|
43723
|
+
if (entry.record === null) {
|
|
43724
|
+
noRecord.set(entry.reason, (noRecord.get(entry.reason) ?? 0) + 1);
|
|
43725
|
+
continue;
|
|
43726
|
+
}
|
|
43727
|
+
withRecord++;
|
|
43728
|
+
const p = entry.record.predicate;
|
|
43729
|
+
blocks[p.block.status]++;
|
|
43730
|
+
for (const claim2 of p.claims) {
|
|
43731
|
+
const col = column(claim2);
|
|
43732
|
+
totals[col]++;
|
|
43733
|
+
types.get(claimType(claim2))[col]++;
|
|
43734
|
+
if (col === "unverifiable" || col === "notRecordCheckable") {
|
|
43735
|
+
const reason = claim2.reason;
|
|
43736
|
+
const seen = unsettled2.get(reason) ?? { column: col, count: 0 };
|
|
43737
|
+
seen.count++;
|
|
43738
|
+
unsettled2.set(reason, seen);
|
|
43739
|
+
}
|
|
43740
|
+
}
|
|
43741
|
+
if (p.advisory !== void 0) {
|
|
43742
|
+
advised++;
|
|
43743
|
+
for (const a of p.advisory.advisories) notes.set(a.note, (notes.get(a.note) ?? 0) + 1);
|
|
43744
|
+
}
|
|
43745
|
+
}
|
|
43746
|
+
const total = totals.supported + totals.unverifiable + totals.notRecordCheckable + totals.contradicted;
|
|
43747
|
+
const result = {
|
|
43748
|
+
mapping: { version: SURVEY_MAPPING_VERSION },
|
|
43749
|
+
pullRequests: {
|
|
43750
|
+
listed: listed2,
|
|
43751
|
+
selected: entries.length,
|
|
43752
|
+
withRecord,
|
|
43753
|
+
blockFound: blocks.found,
|
|
43754
|
+
blockMissing: blocks.missing,
|
|
43755
|
+
blockInvalid: blocks.invalid,
|
|
43756
|
+
blockAmbiguous: blocks.ambiguous,
|
|
43757
|
+
noRecord: [...noRecord.entries()].sort(([a], [b]) => byCodeUnit(a, b)).map(([reason, count]) => ({ reason, count }))
|
|
43758
|
+
},
|
|
43759
|
+
claims: { total, ...totals },
|
|
43760
|
+
byType: CLAIM_TYPES.map((t) => ({ type: t.type, ...types.get(t.type) })),
|
|
43761
|
+
cannotSettle: rate(totals.unverifiable + totals.notRecordCheckable, total),
|
|
43762
|
+
contradictedOfSettled: rate(totals.contradicted, totals.supported + totals.contradicted),
|
|
43763
|
+
unsettled: [...unsettled2.entries()].sort(([a, x], [b, y]) => y.count - x.count || byCodeUnit(a, b)).map(([reason, { column: col, count }]) => ({
|
|
43764
|
+
reason,
|
|
43765
|
+
column: col,
|
|
43766
|
+
count,
|
|
43767
|
+
remedy: remedyForReason(reason, context) ?? null
|
|
43768
|
+
}))
|
|
43769
|
+
};
|
|
43770
|
+
if (advisory) {
|
|
43771
|
+
result.advisory = {
|
|
43772
|
+
pullRequests: advised,
|
|
43773
|
+
notes: [...notes.entries()].sort(([a], [b]) => byCodeUnit(a, b)).map(([note, count]) => ({ note, count }))
|
|
43774
|
+
};
|
|
43775
|
+
}
|
|
43776
|
+
return result;
|
|
43777
|
+
}
|
|
43778
|
+
|
|
43779
|
+
// src/survey/record.ts
|
|
43780
|
+
var SURVEY_FORMAT = "dunstan-survey-0.1-draft";
|
|
43781
|
+
var LISTING_PER_PAGE = 100;
|
|
43782
|
+
var DEFAULT_LAST = 30;
|
|
43783
|
+
var MAX_LAST = 200;
|
|
43784
|
+
function appendListed(listed2, seen, items) {
|
|
43785
|
+
let duplicates = 0;
|
|
43786
|
+
for (const item of items) {
|
|
43787
|
+
if (seen.has(item.number)) duplicates++;
|
|
43788
|
+
else {
|
|
43789
|
+
seen.add(item.number);
|
|
43790
|
+
listed2.push(item);
|
|
43791
|
+
}
|
|
43792
|
+
}
|
|
43793
|
+
return duplicates;
|
|
43794
|
+
}
|
|
43795
|
+
var sinceStart = (since) => `${since}T00:00:00Z`;
|
|
43796
|
+
function selectPullRequests(parameters, listed2) {
|
|
43797
|
+
const authors = new Set(parameters.authors.map((a) => a.toLowerCase()));
|
|
43798
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43799
|
+
return listed2.filter((pr) => {
|
|
43800
|
+
if (seen.has(pr.number)) return false;
|
|
43801
|
+
seen.add(pr.number);
|
|
43802
|
+
return true;
|
|
43803
|
+
}).filter((pr) => parameters.since === null || pr.createdAt >= sinceStart(parameters.since)).filter(
|
|
43804
|
+
(pr) => authors.size === 0 || pr.author !== null && authors.has(pr.author.toLowerCase())
|
|
43805
|
+
).slice(0, parameters.last).map((pr) => pr.number);
|
|
43806
|
+
}
|
|
43807
|
+
function listingStop(parameters, pages, listed2) {
|
|
43808
|
+
const last = pages.at(-1);
|
|
43809
|
+
if (last === void 0) return null;
|
|
43810
|
+
if (last.error !== void 0) return "listing_unreadable";
|
|
43811
|
+
if (selectPullRequests(parameters, listed2).length >= parameters.last) return "selection_full";
|
|
43812
|
+
const since = parameters.since;
|
|
43813
|
+
if (since !== null && listed2.some((pr) => pr.createdAt < sinceStart(since))) {
|
|
43814
|
+
return "since_reached";
|
|
43815
|
+
}
|
|
43816
|
+
if (last.items < LISTING_PER_PAGE) return "listing_ended";
|
|
43817
|
+
return null;
|
|
43818
|
+
}
|
|
43819
|
+
function surveyCommand(parameters, out) {
|
|
43820
|
+
const words2 = [
|
|
43821
|
+
"dunstan",
|
|
43822
|
+
"survey",
|
|
43823
|
+
"--repo",
|
|
43824
|
+
parameters.repository,
|
|
43825
|
+
"--last",
|
|
43826
|
+
`${parameters.last}`
|
|
43827
|
+
];
|
|
43828
|
+
if (parameters.since !== null) words2.push("--since", parameters.since);
|
|
43829
|
+
for (const author of parameters.authors) words2.push("--author", author);
|
|
43830
|
+
if (parameters.report.kind === "pr-comment") {
|
|
43831
|
+
words2.push("--report-comment-author", parameters.report.author);
|
|
43832
|
+
}
|
|
43833
|
+
if (parameters.advisory) words2.push("--advisory");
|
|
43834
|
+
words2.push("--out", out);
|
|
43835
|
+
return words2.join(" ");
|
|
43836
|
+
}
|
|
43837
|
+
function surveyRerun(parameters, out) {
|
|
43838
|
+
return { offline: `dunstan verify ${out}`, online: surveyCommand(parameters, out) };
|
|
43839
|
+
}
|
|
43840
|
+
function surveyDigest(record2) {
|
|
43841
|
+
const { digest: _, ...rest } = record2;
|
|
43842
|
+
return sha256Canonical(rest);
|
|
43843
|
+
}
|
|
43844
|
+
function buildSurveyRecord(input2) {
|
|
43845
|
+
const unsigned = {
|
|
43846
|
+
format: SURVEY_FORMAT,
|
|
43847
|
+
checker: input2.checker,
|
|
43848
|
+
selection: {
|
|
43849
|
+
parameters: input2.parameters,
|
|
43850
|
+
reader: { authenticated: input2.reader.authenticated },
|
|
43851
|
+
listing: input2.listing,
|
|
43852
|
+
selected: selectPullRequests(input2.parameters, input2.listing.pullRequests)
|
|
43853
|
+
},
|
|
43854
|
+
entries: input2.entries,
|
|
43855
|
+
aggregate: aggregate(
|
|
43856
|
+
input2.entries,
|
|
43857
|
+
input2.listing.pullRequests.length,
|
|
43858
|
+
input2.parameters.advisory,
|
|
43859
|
+
input2.reader
|
|
43860
|
+
),
|
|
43861
|
+
rerun: surveyRerun(input2.parameters, input2.out)
|
|
43862
|
+
};
|
|
43863
|
+
return { ...unsigned, digest: { sha256: surveyDigest(unsigned) } };
|
|
43864
|
+
}
|
|
43865
|
+
function serializeSurvey(record2) {
|
|
43866
|
+
return `${JSON.stringify(record2, null, 2)}
|
|
43867
|
+
`;
|
|
43868
|
+
}
|
|
43869
|
+
function isSurveyRecord(value) {
|
|
43870
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && value.format === SURVEY_FORMAT;
|
|
43871
|
+
}
|
|
43872
|
+
|
|
43873
|
+
// src/pipeline/survey.ts
|
|
43874
|
+
var recordFileName = (repository, number4) => `dunstan-${repository.replace("/", "-")}-${number4}.json`;
|
|
43875
|
+
function rateLimited(response) {
|
|
43876
|
+
const s = response.status;
|
|
43877
|
+
return s === 429 || s === 403 && (response.headers.get("x-ratelimit-remaining") === "0" || response.headers.get("retry-after") !== null);
|
|
43878
|
+
}
|
|
43879
|
+
function str2(value) {
|
|
43880
|
+
return typeof value === "string" ? value : null;
|
|
43881
|
+
}
|
|
43882
|
+
function listedOf(item) {
|
|
43883
|
+
if (item === null || typeof item !== "object") return null;
|
|
43884
|
+
const pr = item;
|
|
43885
|
+
const createdAt = str2(pr.created_at);
|
|
43886
|
+
if (typeof pr.number !== "number" || !Number.isSafeInteger(pr.number) || pr.number < 1 || createdAt === null || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?Z$/.test(createdAt)) {
|
|
43887
|
+
return null;
|
|
43888
|
+
}
|
|
43889
|
+
const user = pr.user;
|
|
43890
|
+
const login = str2(user?.login);
|
|
43891
|
+
return {
|
|
43892
|
+
number: pr.number,
|
|
43893
|
+
createdAt,
|
|
43894
|
+
author: login === "" ? null : login,
|
|
43895
|
+
merged: pr.merged_at !== null && pr.merged_at !== void 0
|
|
43896
|
+
};
|
|
43897
|
+
}
|
|
43898
|
+
async function readListing(client, parameters, now, pageBound) {
|
|
43899
|
+
const pages = [];
|
|
43900
|
+
const pullRequests = [];
|
|
43901
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43902
|
+
const done = (end) => ({
|
|
43903
|
+
pageBound,
|
|
43904
|
+
pages,
|
|
43905
|
+
pullRequests,
|
|
43906
|
+
duplicates: pages.reduce((sum, p) => sum + p.duplicates, 0),
|
|
43907
|
+
end
|
|
43908
|
+
});
|
|
43909
|
+
for (let page = 1; page <= pageBound; page++) {
|
|
43910
|
+
const path = `/repos/${parameters.repository}/pulls?state=closed&sort=created&direction=desc&per_page=${LISTING_PER_PAGE}&page=${page}`;
|
|
43911
|
+
const read = await client.read("pull_request", path, { record: false });
|
|
43912
|
+
const entry = {
|
|
43913
|
+
page,
|
|
43914
|
+
locator: `GET ${path}`,
|
|
43915
|
+
sha256: sha256Hex(read.body),
|
|
43916
|
+
readAt: now().toISOString(),
|
|
43917
|
+
items: 0,
|
|
43918
|
+
duplicates: 0
|
|
43919
|
+
};
|
|
43920
|
+
if (!read.ok) {
|
|
43921
|
+
entry.error = read.error;
|
|
43922
|
+
} else {
|
|
43923
|
+
const items = Array.isArray(read.json) ? read.json.map(listedOf) : null;
|
|
43924
|
+
if (items === null || items.some((i) => i === null)) entry.error = "parse";
|
|
43925
|
+
else {
|
|
43926
|
+
entry.items = items.length;
|
|
43927
|
+
entry.duplicates = appendListed(pullRequests, seen, items);
|
|
43928
|
+
}
|
|
43929
|
+
}
|
|
43930
|
+
pages.push(entry);
|
|
43931
|
+
const end = listingStop(parameters, pages, pullRequests);
|
|
43932
|
+
if (end !== null) return done(end);
|
|
43933
|
+
}
|
|
43934
|
+
return done("page_bound");
|
|
43935
|
+
}
|
|
43936
|
+
async function survey(input2) {
|
|
43937
|
+
const { parameters } = input2;
|
|
43938
|
+
const now = input2.now ?? (() => /* @__PURE__ */ new Date());
|
|
43939
|
+
const limited = /* @__PURE__ */ new Set();
|
|
43940
|
+
const baseFetch = input2.fetch ?? fetch;
|
|
43941
|
+
const watched = (async (resource, init) => {
|
|
43942
|
+
const response = await baseFetch(resource, init);
|
|
43943
|
+
if (rateLimited(response)) {
|
|
43944
|
+
const url2 = typeof resource === "string" ? resource : resource instanceof URL ? resource.href : resource.url;
|
|
43945
|
+
limited.add(new URL(url2).pathname);
|
|
43946
|
+
}
|
|
43947
|
+
return response;
|
|
43948
|
+
});
|
|
43949
|
+
const newClient = () => new GitHubClient({
|
|
43950
|
+
token: input2.token,
|
|
43951
|
+
fetch: watched,
|
|
43952
|
+
now,
|
|
43953
|
+
...input2.sleep === void 0 ? {} : { sleep: input2.sleep },
|
|
43954
|
+
...input2.warn === void 0 ? {} : { warn: input2.warn }
|
|
43955
|
+
});
|
|
43956
|
+
const listingClient = newClient();
|
|
43957
|
+
const reader = { authenticated: listingClient.hasToken };
|
|
43958
|
+
const listing = await readListing(listingClient, parameters, now, input2.pageBound ?? MAX_PAGES);
|
|
43959
|
+
const entries = [];
|
|
43960
|
+
for (const number4 of selectPullRequests(parameters, listing.pullRequests)) {
|
|
43961
|
+
const client = newClient();
|
|
43962
|
+
const repository = parameters.repository;
|
|
43963
|
+
const pullPath = `/repos/${repository}/pulls/${number4}`;
|
|
43964
|
+
const commentsPath = `/repos/${repository}/issues/${number4}/comments`;
|
|
43965
|
+
const report = parameters.report;
|
|
43966
|
+
try {
|
|
43967
|
+
const record2 = await checkPullRequest({
|
|
43968
|
+
readers: githubReaders(client),
|
|
43969
|
+
repository,
|
|
43970
|
+
number: number4,
|
|
43971
|
+
report: async (pr) => report.kind === "pr-body" ? reportFromPullRequestBody(pr.repository, pr) : reportFromComment(client, pr.repository, number4, report.author),
|
|
43972
|
+
checker: input2.checker,
|
|
43973
|
+
recordFile: recordFileName(repository, number4),
|
|
43974
|
+
advisory: parameters.advisory
|
|
43975
|
+
});
|
|
43976
|
+
entries.push({ pullRequest: number4, record: record2 });
|
|
43977
|
+
} catch (e) {
|
|
43978
|
+
let reason;
|
|
43979
|
+
if (e instanceof PullRequestMoved) reason = "pull_request_moved";
|
|
43980
|
+
else if (e instanceof PullRequestUnreadable) {
|
|
43981
|
+
reason = limited.has(pullPath) ? "rate_limited" : "pull_request_unreadable";
|
|
43982
|
+
} else if (e instanceof ReportUnavailable) {
|
|
43983
|
+
reason = limited.has(commentsPath) ? "rate_limited" : "report_unavailable";
|
|
43984
|
+
} else throw e;
|
|
43985
|
+
entries.push({ pullRequest: number4, record: null, reason });
|
|
43986
|
+
}
|
|
43987
|
+
}
|
|
43988
|
+
return buildSurveyRecord({
|
|
43989
|
+
checker: input2.checker,
|
|
43990
|
+
parameters,
|
|
43991
|
+
reader,
|
|
43992
|
+
listing,
|
|
43993
|
+
entries,
|
|
43994
|
+
out: input2.out
|
|
43995
|
+
});
|
|
43996
|
+
}
|
|
43997
|
+
|
|
43998
|
+
// src/survey/summary.ts
|
|
43999
|
+
var COLUMN_LABEL = {
|
|
44000
|
+
supported: "Supported",
|
|
44001
|
+
unverifiable: "Unverifiable",
|
|
44002
|
+
notRecordCheckable: "Not record-checkable",
|
|
44003
|
+
contradicted: "Contradicted"
|
|
44004
|
+
};
|
|
44005
|
+
var END_TEXT = {
|
|
44006
|
+
selection_full: "it held enough pull requests for the selection",
|
|
44007
|
+
since_reached: "it reached pull requests created before the --since date",
|
|
44008
|
+
listing_ended: "it ended",
|
|
44009
|
+
listing_unreadable: "a page could not be read; the selection is from the pages read before it",
|
|
44010
|
+
page_bound: "it reached the page bound"
|
|
44011
|
+
};
|
|
44012
|
+
var ANONYMOUS = "Without a token, every tests claim naming a JUnit record and every `closes` claim cannot be settled, so the cannot-settle share is higher than an authenticated read of the same repository would give.";
|
|
44013
|
+
function row(cells) {
|
|
44014
|
+
return `| ${cells.join(" | ")} |`;
|
|
44015
|
+
}
|
|
44016
|
+
function table2(head, rows) {
|
|
44017
|
+
return [row(head), row(head.map(() => "---")), ...rows.map(row)];
|
|
44018
|
+
}
|
|
44019
|
+
function share(r) {
|
|
44020
|
+
return r.value ?? "n/a";
|
|
44021
|
+
}
|
|
44022
|
+
function interval(r) {
|
|
44023
|
+
return r.wilson95 === null ? "n/a" : `${r.wilson95.low}\u2013${r.wilson95.high}`;
|
|
44024
|
+
}
|
|
44025
|
+
function plural2(n, one, many) {
|
|
44026
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
44027
|
+
}
|
|
44028
|
+
function renderSurveySummary(record2) {
|
|
44029
|
+
const { parameters, listing, selected } = record2.selection;
|
|
44030
|
+
const a = record2.aggregate;
|
|
44031
|
+
const lines = [];
|
|
44032
|
+
const scope = [`the ${parameters.last} most recently created closed pull requests`];
|
|
44033
|
+
if (parameters.since !== null) scope.push(`created on or after ${parameters.since}`);
|
|
44034
|
+
if (parameters.authors.length > 0) scope.push(`opened by ${parameters.authors.join(", ")}`);
|
|
44035
|
+
const report = parameters.report.kind === "pr-body" ? "the pull request body" : `the latest comment by ${parameters.report.author}`;
|
|
44036
|
+
lines.push(
|
|
44037
|
+
`# Settleability survey: ${parameters.repository}`,
|
|
44038
|
+
"",
|
|
44039
|
+
`DRAFT (${record2.format}). ${record2.checker.name} ${record2.checker.version}, survey mapping ${a.mapping.version}. Read-only.`,
|
|
44040
|
+
"",
|
|
44041
|
+
`Selection: ${scope.join(", ")}. Report: ${report}. Advisory reader: ${parameters.advisory ? "on" : "off"}.`,
|
|
44042
|
+
"",
|
|
44043
|
+
record2.selection.reader.authenticated ? "GitHub was read with a token (GITHUB_TOKEN set)." : `GitHub was read without a token (GITHUB_TOKEN not set). ${ANONYMOUS}`,
|
|
44044
|
+
"",
|
|
44045
|
+
"This survey reports which claims the record could settle. A claim the record could not settle is neither supported nor contradicted.",
|
|
44046
|
+
"",
|
|
44047
|
+
"## Claims the record could not settle",
|
|
44048
|
+
"",
|
|
44049
|
+
`The record could not settle ${a.cannotSettle.numerator} of ${plural2(a.cannotSettle.denominator, "claim", "claims")}.`,
|
|
44050
|
+
""
|
|
44051
|
+
);
|
|
44052
|
+
if (a.unsettled.length === 0) {
|
|
44053
|
+
lines.push("None.");
|
|
44054
|
+
} else {
|
|
44055
|
+
lines.push(
|
|
44056
|
+
...table2(
|
|
44057
|
+
["Reason", "Column", "Claims", "Remedy"],
|
|
44058
|
+
a.unsettled.map((u) => [
|
|
44059
|
+
`\`${u.reason}\``,
|
|
44060
|
+
COLUMN_LABEL[u.column],
|
|
44061
|
+
u.count,
|
|
44062
|
+
u.remedy ?? "none listed"
|
|
44063
|
+
])
|
|
44064
|
+
)
|
|
44065
|
+
);
|
|
44066
|
+
}
|
|
44067
|
+
lines.push(
|
|
44068
|
+
"",
|
|
44069
|
+
"## Results",
|
|
44070
|
+
"",
|
|
44071
|
+
...table2(
|
|
44072
|
+
["Measure", "Claims", "Of", "Share", "Wilson 95%"],
|
|
44073
|
+
[
|
|
44074
|
+
[
|
|
44075
|
+
"The record could not settle",
|
|
44076
|
+
a.cannotSettle.numerator,
|
|
44077
|
+
`${a.cannotSettle.denominator} claims`,
|
|
44078
|
+
share(a.cannotSettle),
|
|
44079
|
+
interval(a.cannotSettle)
|
|
44080
|
+
],
|
|
44081
|
+
[
|
|
44082
|
+
"Contradicted by the record, of the claims it settled",
|
|
44083
|
+
a.contradictedOfSettled.numerator,
|
|
44084
|
+
`${a.contradictedOfSettled.denominator} settled claims`,
|
|
44085
|
+
share(a.contradictedOfSettled),
|
|
44086
|
+
interval(a.contradictedOfSettled)
|
|
44087
|
+
]
|
|
44088
|
+
]
|
|
44089
|
+
),
|
|
44090
|
+
"",
|
|
44091
|
+
"## By claim type",
|
|
44092
|
+
"",
|
|
44093
|
+
...table2(
|
|
44094
|
+
["Type", "Supported", "Unverifiable", "Not record-checkable", "Contradicted"],
|
|
44095
|
+
[
|
|
44096
|
+
...a.byType.map((t) => {
|
|
44097
|
+
const label = CLAIM_TYPES.find((c) => c.type === t.type)?.label ?? "";
|
|
44098
|
+
return [
|
|
44099
|
+
`${t.type} ${label}`,
|
|
44100
|
+
t.supported,
|
|
44101
|
+
t.unverifiable,
|
|
44102
|
+
t.notRecordCheckable,
|
|
44103
|
+
t.contradicted
|
|
44104
|
+
];
|
|
44105
|
+
}),
|
|
44106
|
+
[
|
|
44107
|
+
`All (${a.claims.total})`,
|
|
44108
|
+
a.claims.supported,
|
|
44109
|
+
a.claims.unverifiable,
|
|
44110
|
+
a.claims.notRecordCheckable,
|
|
44111
|
+
a.claims.contradicted
|
|
44112
|
+
]
|
|
44113
|
+
]
|
|
44114
|
+
)
|
|
44115
|
+
);
|
|
44116
|
+
if (a.advisory !== void 0) {
|
|
44117
|
+
lines.push(
|
|
44118
|
+
"",
|
|
44119
|
+
"## Advisory notes",
|
|
44120
|
+
"",
|
|
44121
|
+
`The advisory reader read the prose of ${plural2(a.advisory.pullRequests, "pull request", "pull requests")}. Its notes never enter either rate, and a possible disagreement is unverified.`,
|
|
44122
|
+
""
|
|
44123
|
+
);
|
|
44124
|
+
if (a.advisory.notes.length === 0) lines.push("No notes.");
|
|
44125
|
+
else {
|
|
44126
|
+
lines.push(
|
|
44127
|
+
...table2(
|
|
44128
|
+
["Note", "As shown", "Count"],
|
|
44129
|
+
a.advisory.notes.map((n) => [`\`${n.note}\``, noteText(n.note), n.count])
|
|
44130
|
+
)
|
|
44131
|
+
);
|
|
44132
|
+
}
|
|
44133
|
+
}
|
|
44134
|
+
const p = a.pullRequests;
|
|
44135
|
+
const context = { hasToken: record2.selection.reader.authenticated };
|
|
44136
|
+
const remedy = (reason) => remedyForReason(reason, context) ?? "";
|
|
44137
|
+
lines.push(
|
|
44138
|
+
"",
|
|
44139
|
+
"## Pull requests",
|
|
44140
|
+
"",
|
|
44141
|
+
...table2(
|
|
44142
|
+
["Pull requests", "Count", "Remedy"],
|
|
44143
|
+
[
|
|
44144
|
+
["Listed", p.listed, ""],
|
|
44145
|
+
["Selected", p.selected, ""],
|
|
44146
|
+
["With a valid block", p.blockFound, ""],
|
|
44147
|
+
["`block_missing`", p.blockMissing, remedy("block_missing")],
|
|
44148
|
+
["`block_invalid`", p.blockInvalid, remedy("block_invalid")],
|
|
44149
|
+
["`block_ambiguous`", p.blockAmbiguous, remedy("block_ambiguous")],
|
|
44150
|
+
...p.noRecord.map((n) => [`No record: \`${n.reason}\``, n.count, remedy(n.reason)])
|
|
44151
|
+
]
|
|
44152
|
+
),
|
|
44153
|
+
"",
|
|
44154
|
+
`The listing was read for ${plural2(listing.pages.length, "page", "pages")} and no further because ${listing.end === "page_bound" ? `it reached the bound of ${listing.pageBound} pages` : END_TEXT[listing.end]}.`
|
|
44155
|
+
);
|
|
44156
|
+
if (listing.duplicates > 0) {
|
|
44157
|
+
const one = listing.duplicates === 1;
|
|
44158
|
+
lines.push(
|
|
44159
|
+
"",
|
|
44160
|
+
`The listing named ${plural2(listing.duplicates, "pull request", "pull requests")} again on a later page, so it changed while it was read: most likely ${plural2(listing.duplicates, "pull request", "pull requests")} closed meanwhile and ${one ? "was" : "were"} not listed, and the selection may be missing ${one ? "it" : "them"}. Each repeated pull request is listed, selected and counted once: the pages held ${listing.pullRequests.length + listing.duplicates} items for ${listing.pullRequests.length} pull requests.`
|
|
44161
|
+
);
|
|
44162
|
+
}
|
|
44163
|
+
if (selected.length < parameters.last) {
|
|
44164
|
+
lines.push(
|
|
44165
|
+
"",
|
|
44166
|
+
`${plural2(selected.length, "pull request was", "pull requests were")} selected, fewer than the ${parameters.last} asked for.`
|
|
44167
|
+
);
|
|
44168
|
+
}
|
|
44169
|
+
lines.push(
|
|
44170
|
+
"",
|
|
44171
|
+
`Verify offline: \`${record2.rerun.offline}\`. Run again: \`${record2.rerun.online}\`.`,
|
|
44172
|
+
""
|
|
44173
|
+
);
|
|
44174
|
+
return lines.join("\n");
|
|
44175
|
+
}
|
|
44176
|
+
|
|
44177
|
+
// src/cli/survey.ts
|
|
44178
|
+
var LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})(?:\[bot\])?$/;
|
|
44179
|
+
function parseLogin(value, flag) {
|
|
44180
|
+
if (!LOGIN.test(value)) throw new UsageError(`${flag} must be a GitHub login`);
|
|
44181
|
+
return value;
|
|
44182
|
+
}
|
|
44183
|
+
function parseSince(value) {
|
|
44184
|
+
const day = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
44185
|
+
const valid = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value) && !Number.isNaN(day.getTime()) && day.toISOString().slice(0, 10) === value;
|
|
44186
|
+
if (!valid) throw new UsageError("--since must be a date, YYYY-MM-DD (UTC)");
|
|
44187
|
+
return value;
|
|
44188
|
+
}
|
|
44189
|
+
async function survey2(args, io) {
|
|
44190
|
+
const { values } = parseArgs5({
|
|
44191
|
+
args,
|
|
44192
|
+
strict: true,
|
|
44193
|
+
allowPositionals: false,
|
|
44194
|
+
options: {
|
|
44195
|
+
repo: { type: "string" },
|
|
44196
|
+
last: { type: "string" },
|
|
44197
|
+
since: { type: "string" },
|
|
44198
|
+
author: { type: "string", multiple: true },
|
|
44199
|
+
"report-comment-author": { type: "string" },
|
|
44200
|
+
advisory: { type: "boolean" },
|
|
44201
|
+
out: { type: "string" },
|
|
44202
|
+
summary: { type: "string" }
|
|
44203
|
+
}
|
|
44204
|
+
});
|
|
44205
|
+
if (values.repo === void 0 || !REPOSITORY.test(values.repo)) {
|
|
44206
|
+
throw new UsageError("--repo <owner/name> is required");
|
|
44207
|
+
}
|
|
44208
|
+
if (values.out === void 0) throw new UsageError("--out <survey.json> is required");
|
|
44209
|
+
const last = values.last === void 0 ? DEFAULT_LAST : parsePositiveInt(values.last, "--last");
|
|
44210
|
+
if (last > MAX_LAST) throw new UsageError(`--last is at most ${MAX_LAST}`);
|
|
44211
|
+
const commentAuthor = values["report-comment-author"];
|
|
44212
|
+
const parameters = {
|
|
44213
|
+
repository: values.repo,
|
|
44214
|
+
last,
|
|
44215
|
+
since: values.since === void 0 ? null : parseSince(values.since),
|
|
44216
|
+
authors: (values.author ?? []).map((a) => parseLogin(a, "--author")),
|
|
44217
|
+
report: commentAuthor === void 0 ? { kind: "pr-body" } : { kind: "pr-comment", author: parseLogin(commentAuthor, "--report-comment-author") },
|
|
44218
|
+
advisory: values.advisory === true
|
|
44219
|
+
};
|
|
44220
|
+
const token = io.env.GITHUB_TOKEN;
|
|
44221
|
+
if (token === void 0 || token === "") {
|
|
44222
|
+
io.err(
|
|
44223
|
+
"dunstan: GITHUB_TOKEN is not set; reading anonymously (GitHub serves no artifact to an anonymous reader)\n"
|
|
44224
|
+
);
|
|
44225
|
+
}
|
|
44226
|
+
const record2 = await survey({
|
|
44227
|
+
parameters,
|
|
44228
|
+
checker: checkerIdentity(io.artifact),
|
|
44229
|
+
out: basename4(values.out),
|
|
44230
|
+
token,
|
|
44231
|
+
...io.fetch === void 0 ? {} : { fetch: io.fetch },
|
|
44232
|
+
...io.now === void 0 ? {} : { now: io.now },
|
|
44233
|
+
warn: (message) => io.err(`dunstan: ${message}
|
|
44234
|
+
`)
|
|
44235
|
+
});
|
|
44236
|
+
writeFileSync5(values.out, serializeSurvey(record2));
|
|
44237
|
+
const summary = renderSurveySummary(record2);
|
|
44238
|
+
if (values.summary === void 0) {
|
|
44239
|
+
io.out(summary);
|
|
44240
|
+
io.err(`dunstan: survey record: ${values.out}
|
|
44241
|
+
`);
|
|
44242
|
+
} else {
|
|
44243
|
+
writeFileSync5(values.summary, summary);
|
|
44244
|
+
io.out(`survey record: ${values.out}
|
|
44245
|
+
summary: ${values.summary}
|
|
44246
|
+
`);
|
|
44247
|
+
}
|
|
44248
|
+
return 0;
|
|
44249
|
+
}
|
|
44250
|
+
|
|
43337
44251
|
// src/cli/template.ts
|
|
43338
|
-
import { parseArgs as
|
|
43339
|
-
|
|
43340
|
-
|
|
44252
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
44253
|
+
|
|
44254
|
+
// src/template/tests-ci.ts
|
|
44255
|
+
var plural3 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
44256
|
+
function unsettled(reason, why, remedy) {
|
|
44257
|
+
const notes = [`tests are not declared: ${why} (${reason}).`];
|
|
44258
|
+
if (remedy !== void 0) notes.push(`remedy: ${remedy}`);
|
|
44259
|
+
return { status: "unsettled", reason, notes };
|
|
44260
|
+
}
|
|
44261
|
+
async function testsFromCi(client, input2) {
|
|
44262
|
+
const { repository, headCommit: head, record: record2 } = input2;
|
|
44263
|
+
if (!client.hasToken) {
|
|
44264
|
+
return unsettled(
|
|
44265
|
+
"no_token",
|
|
44266
|
+
"GITHUB_TOKEN is not set, and GitHub answers an artifact download only with a token, even on a public repository",
|
|
44267
|
+
"set GITHUB_TOKEN to a token that can read the repository, with actions: read, and run dunstan template --tests-ci again."
|
|
44268
|
+
);
|
|
44269
|
+
}
|
|
44270
|
+
if (!input2.pushed) {
|
|
44271
|
+
return unsettled(
|
|
44272
|
+
"not_pushed",
|
|
44273
|
+
`HEAD ${head} is not on any remote-tracking branch, so no CI run can have tested it`,
|
|
44274
|
+
"push it, wait for CI to finish, and run dunstan template --tests-ci again (run git fetch first if it was pushed from elsewhere)."
|
|
44275
|
+
);
|
|
44276
|
+
}
|
|
44277
|
+
const observed = { runsNotCompleted: 0 };
|
|
44278
|
+
const evidence = await readTestRecord(client, repository, record2, head, observed);
|
|
44279
|
+
const named = `the artifact ${record2.artifact} holding ${record2.path} from job ${record2.job}`;
|
|
44280
|
+
const pending = observed.runsNotCompleted > 0 ? `CI has not finished: ${plural3(observed.runsNotCompleted, "run", "runs")} of ${record2.workflow} at ${head} ${observed.runsNotCompleted === 1 ? "is" : "are"} not completed` : void 0;
|
|
44281
|
+
const again = "run dunstan template --tests-ci again once CI has finished on this commit.";
|
|
44282
|
+
if (evidence.status !== "ok") {
|
|
44283
|
+
const reason = evidence.status === "unreadable" ? `source_unreadable:${evidence.source}` : `evidence_field_unpopulated:${evidence.field}`;
|
|
44284
|
+
if (pending !== void 0) return unsettled(reason, pending, again);
|
|
44285
|
+
const why = reason === "source_unreadable:artifact" ? `a run of ${record2.workflow} at ${head} uploaded ${record2.artifact}, and it could not be read` : `the workflow runs of ${record2.workflow} at ${head} could not be read`;
|
|
44286
|
+
return unsettled(
|
|
44287
|
+
reason,
|
|
44288
|
+
why,
|
|
44289
|
+
remedyForReason(reason, { hasToken: true }) ?? "run dunstan template --tests-ci again."
|
|
44290
|
+
);
|
|
44291
|
+
}
|
|
44292
|
+
if (evidence.runs.length === 0) {
|
|
44293
|
+
if (pending !== void 0) {
|
|
44294
|
+
return unsettled(
|
|
44295
|
+
"record_not_found",
|
|
44296
|
+
`${pending}, and no completed run uploaded ${named}`,
|
|
44297
|
+
again
|
|
44298
|
+
);
|
|
44299
|
+
}
|
|
44300
|
+
return unsettled(
|
|
44301
|
+
"record_not_found",
|
|
44302
|
+
`no run of ${record2.workflow} at ${head} uploaded ${named}`,
|
|
44303
|
+
remedyForReason("record_not_found", { hasToken: true })
|
|
44304
|
+
);
|
|
44305
|
+
}
|
|
44306
|
+
const executed = new Set(evidence.runs.map((r) => r.executed));
|
|
44307
|
+
const failed = new Set(evidence.runs.map((r) => r.failed));
|
|
44308
|
+
if (executed.size > 1 || failed.size > 1) {
|
|
44309
|
+
const counts = evidence.runs.map((r) => `run ${r.runId}: ${r.executed} executed, ${r.failed} failed`).join("; ");
|
|
44310
|
+
return unsettled(
|
|
44311
|
+
"record_ambiguous",
|
|
44312
|
+
`the runs of ${record2.workflow} at ${head} that uploaded ${record2.artifact} disagree (${counts}), and the gate never picks one`,
|
|
44313
|
+
"make the runs agree (re-run the one that differs), or name a job only one of them runs."
|
|
44314
|
+
);
|
|
44315
|
+
}
|
|
44316
|
+
const run = evidence.runs[0];
|
|
44317
|
+
const runs = evidence.runs.map((r) => r.runId).join(", ");
|
|
44318
|
+
const notes = [
|
|
44319
|
+
`tests: ${run.executed} executed and ${run.failed} failed in ${record2.path} in the artifact ${record2.artifact} of job ${record2.job} in ${record2.workflow} at ${head}, read from CI as the gate reads it (${evidence.runs.length === 1 ? "run" : "runs"} ${runs}).`
|
|
44320
|
+
];
|
|
44321
|
+
if (pending !== void 0) {
|
|
44322
|
+
notes.push(
|
|
44323
|
+
`${pending}; a run that finishes later with other counts makes the gate's read ambiguous. Run dunstan template --tests-ci again once CI has finished.`
|
|
44324
|
+
);
|
|
44325
|
+
}
|
|
44326
|
+
return {
|
|
44327
|
+
status: "settled",
|
|
44328
|
+
run: { command: input2.command, count: run.executed, failures: run.failed, record: record2 },
|
|
44329
|
+
notes
|
|
44330
|
+
};
|
|
44331
|
+
}
|
|
44332
|
+
|
|
44333
|
+
// src/cli/template.ts
|
|
44334
|
+
var RECORD_FLAGS = [
|
|
43341
44335
|
"tests-workflow",
|
|
43342
44336
|
"tests-job",
|
|
43343
44337
|
"tests-artifact",
|
|
43344
44338
|
"tests-artifact-path"
|
|
43345
44339
|
];
|
|
44340
|
+
var DEFAULT_ARTIFACT = "dunstan-junit";
|
|
44341
|
+
var DEFAULT_ARTIFACT_PATH = "junit.xml";
|
|
43346
44342
|
async function template(args, io) {
|
|
43347
|
-
const { values } =
|
|
44343
|
+
const { values } = parseArgs6({
|
|
43348
44344
|
args,
|
|
43349
44345
|
strict: true,
|
|
43350
44346
|
allowPositionals: false,
|
|
43351
44347
|
options: {
|
|
43352
44348
|
base: { type: "string" },
|
|
44349
|
+
"tests-ci": { type: "boolean" },
|
|
44350
|
+
"tests-local": { type: "string" },
|
|
43353
44351
|
"tests-junit": { type: "string" },
|
|
43354
|
-
|
|
44352
|
+
"tests-command": { type: "string" },
|
|
44353
|
+
repo: { type: "string" },
|
|
44354
|
+
...Object.fromEntries(RECORD_FLAGS.map((f) => [f, { type: "string" }]))
|
|
43355
44355
|
}
|
|
43356
44356
|
});
|
|
43357
44357
|
const v = values;
|
|
44358
|
+
const flag = (name) => v[name];
|
|
44359
|
+
const ci = v["tests-ci"] === true;
|
|
44360
|
+
const modes = [ci, flag("tests-local") !== void 0, flag("tests-junit") !== void 0];
|
|
44361
|
+
if (modes.filter(Boolean).length > 1) {
|
|
44362
|
+
throw new UsageError("give one of --tests-ci, --tests-local or --tests-junit");
|
|
44363
|
+
}
|
|
44364
|
+
if (flag("repo") !== void 0 && !ci) throw new UsageError("--repo goes with --tests-ci");
|
|
44365
|
+
const given = ["tests-command", ...RECORD_FLAGS].filter((f) => flag(f) !== void 0);
|
|
44366
|
+
const cwd = io.cwd ?? process.cwd();
|
|
43358
44367
|
let tests;
|
|
43359
|
-
|
|
43360
|
-
|
|
43361
|
-
|
|
43362
|
-
|
|
44368
|
+
if (ci) {
|
|
44369
|
+
const missing = ["tests-command", "tests-workflow", "tests-job"].filter(
|
|
44370
|
+
(f) => flag(f) === void 0
|
|
44371
|
+
);
|
|
44372
|
+
if (missing.length > 0) {
|
|
44373
|
+
throw new UsageError(`--tests-ci needs ${missing.map((f) => `--${f}`).join(", ")}`);
|
|
44374
|
+
}
|
|
44375
|
+
const repo = flag("repo");
|
|
44376
|
+
if (repo !== void 0 && !REPOSITORY.test(repo)) {
|
|
44377
|
+
throw new UsageError("--repo must be <owner/name>");
|
|
44378
|
+
}
|
|
44379
|
+
const head = headCommit(cwd);
|
|
44380
|
+
const repository = repo ?? originRepository(cwd);
|
|
44381
|
+
const client = new GitHubClient({
|
|
44382
|
+
token: io.env.GITHUB_TOKEN,
|
|
44383
|
+
...io.fetch === void 0 ? {} : { fetch: io.fetch },
|
|
44384
|
+
warn: (message) => io.err(`dunstan: ${message}
|
|
44385
|
+
`)
|
|
44386
|
+
});
|
|
44387
|
+
const read = await testsFromCi(client, {
|
|
44388
|
+
repository,
|
|
44389
|
+
headCommit: head,
|
|
44390
|
+
command: flag("tests-command"),
|
|
44391
|
+
pushed: isPushed(cwd, head),
|
|
44392
|
+
record: {
|
|
44393
|
+
kind: "junit",
|
|
44394
|
+
workflow: flag("tests-workflow"),
|
|
44395
|
+
job: flag("tests-job"),
|
|
44396
|
+
artifact: flag("tests-artifact") ?? DEFAULT_ARTIFACT,
|
|
44397
|
+
path: flag("tests-artifact-path") ?? DEFAULT_ARTIFACT_PATH
|
|
44398
|
+
}
|
|
44399
|
+
});
|
|
44400
|
+
tests = { kind: "ci", read };
|
|
44401
|
+
} else if (flag("tests-local") !== void 0) {
|
|
44402
|
+
if (flag("tests-command") === void 0) {
|
|
44403
|
+
throw new UsageError("--tests-local needs --tests-command <cmd>");
|
|
43363
44404
|
}
|
|
43364
|
-
|
|
43365
|
-
|
|
44405
|
+
const extra = RECORD_FLAGS.find((f) => flag(f) !== void 0);
|
|
44406
|
+
if (extra !== void 0) throw new UsageError(`--${extra} does not go with --tests-local`);
|
|
44407
|
+
tests = {
|
|
44408
|
+
kind: "local",
|
|
44409
|
+
junit: flag("tests-local"),
|
|
44410
|
+
command: flag("tests-command")
|
|
44411
|
+
};
|
|
44412
|
+
} else if (flag("tests-junit") !== void 0) {
|
|
44413
|
+
const missing = ["tests-command", ...RECORD_FLAGS].filter(
|
|
44414
|
+
(f) => flag(f) === void 0
|
|
44415
|
+
);
|
|
43366
44416
|
if (missing.length > 0) {
|
|
43367
44417
|
throw new UsageError(
|
|
43368
44418
|
`--tests-junit needs the CI record its counts are checked against: ${missing.map((f) => `--${f}`).join(", ")}`
|
|
43369
44419
|
);
|
|
43370
44420
|
}
|
|
43371
44421
|
tests = {
|
|
43372
|
-
|
|
43373
|
-
|
|
44422
|
+
kind: "junit",
|
|
44423
|
+
junit: flag("tests-junit"),
|
|
44424
|
+
command: flag("tests-command"),
|
|
43374
44425
|
record: {
|
|
43375
|
-
workflow:
|
|
43376
|
-
job:
|
|
43377
|
-
artifact:
|
|
43378
|
-
path:
|
|
44426
|
+
workflow: flag("tests-workflow"),
|
|
44427
|
+
job: flag("tests-job"),
|
|
44428
|
+
artifact: flag("tests-artifact"),
|
|
44429
|
+
path: flag("tests-artifact-path")
|
|
43379
44430
|
}
|
|
43380
44431
|
};
|
|
44432
|
+
} else if (given.length > 0) {
|
|
44433
|
+
throw new UsageError(`--${given[0]} goes with --tests-ci, --tests-local or --tests-junit`);
|
|
43381
44434
|
}
|
|
43382
|
-
const t = templateBlock({ cwd
|
|
44435
|
+
const t = templateBlock({ cwd, base: flag("base"), tests });
|
|
43383
44436
|
io.out(renderBlock(t.block));
|
|
43384
44437
|
for (const note of t.notes) io.err(`dunstan: ${note}
|
|
43385
44438
|
`);
|
|
43386
|
-
return 0;
|
|
44439
|
+
return tests?.kind === "ci" && tests.read.status === "unsettled" ? EXIT.unverifiable : 0;
|
|
43387
44440
|
}
|
|
43388
44441
|
|
|
43389
44442
|
// src/cli/verify.ts
|
|
43390
|
-
import { readFileSync as
|
|
43391
|
-
import { parseArgs as
|
|
44443
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
44444
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
43392
44445
|
|
|
43393
44446
|
// src/record/verify.ts
|
|
43394
44447
|
function same(a, b) {
|
|
@@ -43443,10 +44496,10 @@ function verifyRecord(value) {
|
|
|
43443
44496
|
const claims = claimsFor(p.block, evidence, p.subject.repository);
|
|
43444
44497
|
if (!same(p.claims, claims)) {
|
|
43445
44498
|
const index = claims.findIndex((c, i) => !same(c, p.claims[i]));
|
|
43446
|
-
const
|
|
44499
|
+
const at2 = index === -1 ? claims.length : index;
|
|
43447
44500
|
problems.push({
|
|
43448
|
-
member: `/predicate/claims/${
|
|
43449
|
-
message: `recorded ${canonicalize(p.claims[
|
|
44501
|
+
member: `/predicate/claims/${at2}`,
|
|
44502
|
+
message: `recorded ${canonicalize(p.claims[at2] ?? null)}, recomputed ${canonicalize(claims[at2] ?? null)}`
|
|
43450
44503
|
});
|
|
43451
44504
|
}
|
|
43452
44505
|
differ("/predicate/verdict", p.verdict, overallVerdict(p.block.status, claims));
|
|
@@ -43503,9 +44556,162 @@ function verifyRecord(value) {
|
|
|
43503
44556
|
return { ok: problems.length === 0, problems, record: record2 };
|
|
43504
44557
|
}
|
|
43505
44558
|
|
|
44559
|
+
// src/survey/verify.ts
|
|
44560
|
+
var text = (value) => canonicalize(value ?? null);
|
|
44561
|
+
function differences(a, b, pointer) {
|
|
44562
|
+
const isObject2 = (v) => v !== null && typeof v === "object";
|
|
44563
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
44564
|
+
const out = [];
|
|
44565
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
44566
|
+
out.push(...differences(a[i], b[i], `${pointer}/${i}`));
|
|
44567
|
+
}
|
|
44568
|
+
return out;
|
|
44569
|
+
}
|
|
44570
|
+
if (isObject2(a) && isObject2(b) && !Array.isArray(a) && !Array.isArray(b)) {
|
|
44571
|
+
const ao = a;
|
|
44572
|
+
const bo = b;
|
|
44573
|
+
const keys = [.../* @__PURE__ */ new Set([...Object.keys(ao), ...Object.keys(bo)])].sort();
|
|
44574
|
+
return keys.flatMap((k) => differences(ao[k], bo[k], `${pointer}/${escapePointerToken(k)}`));
|
|
44575
|
+
}
|
|
44576
|
+
return text(a) === text(b) ? [] : [pointer];
|
|
44577
|
+
}
|
|
44578
|
+
function at(value, pointer, base) {
|
|
44579
|
+
let v = value;
|
|
44580
|
+
for (const token of pointer.slice(base.length).split("/").slice(1)) {
|
|
44581
|
+
const key = token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
44582
|
+
v = v !== null && typeof v === "object" ? v[key] : void 0;
|
|
44583
|
+
}
|
|
44584
|
+
return v;
|
|
44585
|
+
}
|
|
44586
|
+
function verifySurveyRecord(value) {
|
|
44587
|
+
const schemaErrors = validateSurveyRecord(value);
|
|
44588
|
+
if (schemaErrors.length > 0) {
|
|
44589
|
+
return {
|
|
44590
|
+
ok: false,
|
|
44591
|
+
problems: schemaErrors.map((e) => ({
|
|
44592
|
+
member: e.pointer,
|
|
44593
|
+
message: `schema_violation (${e.keyword}): ${e.message}`
|
|
44594
|
+
}))
|
|
44595
|
+
};
|
|
44596
|
+
}
|
|
44597
|
+
const record2 = value;
|
|
44598
|
+
const problems = [];
|
|
44599
|
+
const differ = (member, recorded, recomputed) => {
|
|
44600
|
+
for (const pointer of differences(recorded, recomputed, member)) {
|
|
44601
|
+
problems.push({
|
|
44602
|
+
member: pointer,
|
|
44603
|
+
message: `recorded ${text(at(recorded, pointer, member))}, recomputed ${text(at(recomputed, pointer, member))}`
|
|
44604
|
+
});
|
|
44605
|
+
}
|
|
44606
|
+
};
|
|
44607
|
+
const { checker, selection, entries } = record2;
|
|
44608
|
+
if (checker.name !== CHECKER_NAME || checker.version !== CHECKER_VERSION) {
|
|
44609
|
+
problems.push({
|
|
44610
|
+
member: "/checker",
|
|
44611
|
+
message: `survey was written by ${checker.name} ${checker.version}; this is ${CHECKER_NAME} ${CHECKER_VERSION}`
|
|
44612
|
+
});
|
|
44613
|
+
return { ok: false, problems, record: record2 };
|
|
44614
|
+
}
|
|
44615
|
+
const { parameters, listing } = selection;
|
|
44616
|
+
const firstAt = /* @__PURE__ */ new Map();
|
|
44617
|
+
listing.pullRequests.forEach((pr, i) => {
|
|
44618
|
+
const first = firstAt.get(pr.number);
|
|
44619
|
+
if (first === void 0) firstAt.set(pr.number, i);
|
|
44620
|
+
else {
|
|
44621
|
+
problems.push({
|
|
44622
|
+
member: `/selection/listing/pullRequests/${i}`,
|
|
44623
|
+
message: `pull request ${pr.number} is listed again (first at /selection/listing/pullRequests/${first}); the listing holds each number once`
|
|
44624
|
+
});
|
|
44625
|
+
}
|
|
44626
|
+
});
|
|
44627
|
+
const held = listing.pages.reduce((sum, page) => sum + page.items, 0);
|
|
44628
|
+
const repeated = listing.pages.reduce((sum, page) => sum + page.duplicates, 0);
|
|
44629
|
+
listing.pages.forEach((page, i) => {
|
|
44630
|
+
if (page.duplicates > page.items) {
|
|
44631
|
+
problems.push({
|
|
44632
|
+
member: `/selection/listing/pages/${i}/duplicates`,
|
|
44633
|
+
message: `recorded ${page.duplicates}, more than the page's ${page.items} items`
|
|
44634
|
+
});
|
|
44635
|
+
}
|
|
44636
|
+
});
|
|
44637
|
+
differ("/selection/listing/duplicates", listing.duplicates, repeated);
|
|
44638
|
+
differ(
|
|
44639
|
+
"/selection/listing/pullRequests/length",
|
|
44640
|
+
listing.pullRequests.length,
|
|
44641
|
+
held - listing.duplicates
|
|
44642
|
+
);
|
|
44643
|
+
let read = 0;
|
|
44644
|
+
listing.pages.forEach((page, i) => {
|
|
44645
|
+
read += page.items - page.duplicates;
|
|
44646
|
+
if (i === listing.pages.length - 1) return;
|
|
44647
|
+
const stop2 = listingStop(
|
|
44648
|
+
parameters,
|
|
44649
|
+
listing.pages.slice(0, i + 1),
|
|
44650
|
+
listing.pullRequests.slice(0, read)
|
|
44651
|
+
);
|
|
44652
|
+
if (stop2 !== null) {
|
|
44653
|
+
problems.push({
|
|
44654
|
+
member: `/selection/listing/pages/${i + 1}`,
|
|
44655
|
+
message: `the listing ended at page ${page.page} (${stop2}); no later page is read`
|
|
44656
|
+
});
|
|
44657
|
+
}
|
|
44658
|
+
});
|
|
44659
|
+
const stop = listingStop(parameters, listing.pages, listing.pullRequests);
|
|
44660
|
+
const end = stop ?? (listing.pages.length === listing.pageBound ? "page_bound" : null);
|
|
44661
|
+
if (end === null) {
|
|
44662
|
+
problems.push({
|
|
44663
|
+
member: "/selection/listing/end",
|
|
44664
|
+
message: `recorded ${text(listing.end)}, recomputed none: the listing was not read to an end`
|
|
44665
|
+
});
|
|
44666
|
+
} else {
|
|
44667
|
+
differ("/selection/listing/end", listing.end, end);
|
|
44668
|
+
}
|
|
44669
|
+
if (listing.pages.length > listing.pageBound) {
|
|
44670
|
+
problems.push({
|
|
44671
|
+
member: "/selection/listing/pages",
|
|
44672
|
+
message: `${listing.pages.length} pages read, past the bound of ${listing.pageBound}`
|
|
44673
|
+
});
|
|
44674
|
+
}
|
|
44675
|
+
const selected = selectPullRequests(parameters, listing.pullRequests);
|
|
44676
|
+
differ("/selection/selected", selection.selected, selected);
|
|
44677
|
+
differ(
|
|
44678
|
+
"/entries/pullRequests",
|
|
44679
|
+
entries.map((e) => e.pullRequest),
|
|
44680
|
+
selected
|
|
44681
|
+
);
|
|
44682
|
+
entries.forEach((entry, i) => {
|
|
44683
|
+
if (entry.record === null) return;
|
|
44684
|
+
const base = `/entries/${i}/record`;
|
|
44685
|
+
const result = verifyRecord(entry.record);
|
|
44686
|
+
for (const p of result.problems)
|
|
44687
|
+
problems.push({ member: `${base}${p.member}`, message: p.message });
|
|
44688
|
+
const subject = entry.record.predicate.subject;
|
|
44689
|
+
differ(`${base}/predicate/subject/pullRequest`, subject.pullRequest, entry.pullRequest);
|
|
44690
|
+
differ(
|
|
44691
|
+
`${base}/predicate/subject/repository`,
|
|
44692
|
+
subject.repository.toLowerCase(),
|
|
44693
|
+
parameters.repository.toLowerCase()
|
|
44694
|
+
);
|
|
44695
|
+
differ(`${base}/predicate/checker`, entry.record.predicate.checker, checker);
|
|
44696
|
+
});
|
|
44697
|
+
try {
|
|
44698
|
+
differ(
|
|
44699
|
+
"/aggregate",
|
|
44700
|
+
record2.aggregate,
|
|
44701
|
+
aggregate(entries, listing.pullRequests.length, parameters.advisory, selection.reader)
|
|
44702
|
+
);
|
|
44703
|
+
} catch (e) {
|
|
44704
|
+
problems.push({ member: "/aggregate", message: `not recomputed: ${e.message}` });
|
|
44705
|
+
}
|
|
44706
|
+
const out = record2.rerun.offline.startsWith("dunstan verify ") ? record2.rerun.offline.slice("dunstan verify ".length) : "";
|
|
44707
|
+
differ("/rerun", record2.rerun, surveyRerun(parameters, out));
|
|
44708
|
+
differ("/digest/sha256", record2.digest.sha256, surveyDigest(record2));
|
|
44709
|
+
return { ok: problems.length === 0, problems, record: record2 };
|
|
44710
|
+
}
|
|
44711
|
+
|
|
43506
44712
|
// src/cli/verify.ts
|
|
43507
44713
|
async function verify(args, io) {
|
|
43508
|
-
const { values, positionals } =
|
|
44714
|
+
const { values, positionals } = parseArgs7({
|
|
43509
44715
|
args,
|
|
43510
44716
|
strict: true,
|
|
43511
44717
|
allowPositionals: true,
|
|
@@ -43518,7 +44724,7 @@ async function verify(args, io) {
|
|
|
43518
44724
|
throw new UsageError("--sig and --allowed-signers go together");
|
|
43519
44725
|
}
|
|
43520
44726
|
const path = positionals[0];
|
|
43521
|
-
const bytes =
|
|
44727
|
+
const bytes = readFileSync7(path);
|
|
43522
44728
|
let value;
|
|
43523
44729
|
try {
|
|
43524
44730
|
value = parseStrictJson(new TextDecoder("utf-8").decode(bytes));
|
|
@@ -43528,6 +44734,25 @@ async function verify(args, io) {
|
|
|
43528
44734
|
`);
|
|
43529
44735
|
return 1;
|
|
43530
44736
|
}
|
|
44737
|
+
if (isSurveyRecord(value)) {
|
|
44738
|
+
if (sig !== void 0) throw new UsageError("a survey record is not signed");
|
|
44739
|
+
const survey3 = verifySurveyRecord(value);
|
|
44740
|
+
if (!survey3.ok) {
|
|
44741
|
+
io.out(`NOT verified: ${path}
|
|
44742
|
+
`);
|
|
44743
|
+
for (const problem of survey3.problems) {
|
|
44744
|
+
io.out(` ${problem.member || "/"}: ${problem.message}
|
|
44745
|
+
`);
|
|
44746
|
+
}
|
|
44747
|
+
return 1;
|
|
44748
|
+
}
|
|
44749
|
+
const s = survey3.record;
|
|
44750
|
+
io.out(
|
|
44751
|
+
`verified: ${path}: survey of ${s?.entries.length} pull requests; every embedded record, the selection, the aggregate and the digest recompute
|
|
44752
|
+
`
|
|
44753
|
+
);
|
|
44754
|
+
return 0;
|
|
44755
|
+
}
|
|
43531
44756
|
const result = verifyRecord(value);
|
|
43532
44757
|
const problems = [...result.problems];
|
|
43533
44758
|
const assurance = result.record?.predicate.assurance;
|
|
@@ -43581,9 +44806,16 @@ usage:
|
|
|
43581
44806
|
dunstan verify <record.json> [--sig <file> --allowed-signers <file>]
|
|
43582
44807
|
dunstan rerun <record.json> [--out <record.json>] [--exclude-check-run <id>]...
|
|
43583
44808
|
dunstan template [--base <ref>]
|
|
43584
|
-
[--tests-
|
|
44809
|
+
[--tests-ci --tests-command <cmd> --tests-workflow <path> --tests-job <name>
|
|
44810
|
+
[--tests-artifact <name>] [--tests-artifact-path <path>] [--repo <owner/name>]
|
|
44811
|
+
| --tests-local <file> --tests-command <cmd>
|
|
44812
|
+
| --tests-junit <file> --tests-command <cmd> --tests-workflow <path>
|
|
43585
44813
|
--tests-job <name> --tests-artifact <name> --tests-artifact-path <path>]
|
|
43586
44814
|
dunstan suggest [--report-file <path|->]
|
|
44815
|
+
dunstan survey --repo <owner/name> --out <survey.json> [--summary <survey.md>]
|
|
44816
|
+
[--last <n>] [--since <YYYY-MM-DD>] [--author <login>]...
|
|
44817
|
+
[--report-comment-author <login>] [--advisory]
|
|
44818
|
+
dunstan junit-merge <file>... --out <file>
|
|
43587
44819
|
dunstan mcp
|
|
43588
44820
|
|
|
43589
44821
|
check reads the pull request, the named report and the evidence its block needs, writes the
|
|
@@ -43593,15 +44825,30 @@ check reads the pull request, the named report and the evidence its block ne
|
|
|
43593
44825
|
verify offline: recomputes claims, verdict and digests from the record and compares them.
|
|
43594
44826
|
rerun online: re-reads the sources for the recorded subject and block; writes a new record.
|
|
43595
44827
|
template local git: prints a dunstan-handback block for HEAD, its files against the merge base
|
|
43596
|
-
with --base (default: the remote's default branch). Tests
|
|
43597
|
-
the CI record
|
|
44828
|
+
with --base (default: the remote's default branch). Tests: --tests-ci reads the counts
|
|
44829
|
+
at HEAD from the CI record check reads (artifact dunstan-junit, file junit.xml unless
|
|
44830
|
+
given; the repository origin names on github.com unless --repo), with GITHUB_TOKEN;
|
|
44831
|
+
--tests-local declares a local JUnit file's counts as a local run; --tests-junit
|
|
44832
|
+
declares them against a CI record. Only --tests-ci reads the network. References,
|
|
44833
|
+
checks and mergedAt never (docs/agents.md, docs/ci-test-records.md).
|
|
43598
44834
|
suggest offline: lists sentences in the report's prose (a file, or stdin) that look like claims
|
|
43599
44835
|
its block does not declare, to declare or reword. Never a verdict (docs/agents.md).
|
|
44836
|
+
survey read-only, DRAFT (docs/survey.md): checks the --last (default 30, at most 200) most
|
|
44837
|
+
recently created closed pull requests as check does, the report in each body or the
|
|
44838
|
+
named author's latest comment; writes the survey record and a Markdown summary of the
|
|
44839
|
+
claims the record could not settle, why, and the contradicted share of the claims it
|
|
44840
|
+
settled. verify recomputes a survey record. GITHUB_TOKEN is optional; the record says
|
|
44841
|
+
whether it was set.
|
|
44842
|
+
junit-merge
|
|
44843
|
+
offline: one JUnit file from several (Maven Surefire, Gradle), inputs in code-unit
|
|
44844
|
+
order of path, each copied whole; refused unless its counts are the inputs' sum.
|
|
43600
44845
|
mcp serves dunstan_check_handback and dunstan_suggest_declarations over stdio
|
|
43601
44846
|
(docs/mcp.md): check's checker and suggest, called by an agent before it says done.
|
|
43602
44847
|
|
|
43603
|
-
exit codes: 0 pass (verify: verified; template: printed; suggest: always
|
|
43604
|
-
|
|
44848
|
+
exit codes: 0 pass (verify: verified; template: printed; suggest: always; survey: written;
|
|
44849
|
+
junit-merge: written), 1 fail (verify: not verified), 2 unverifiable (template:
|
|
44850
|
+
printed without the tests --tests-ci asked for), 3 usage or internal error, git
|
|
44851
|
+
cannot answer (template), or refused (junit-merge)
|
|
43605
44852
|
`;
|
|
43606
44853
|
async function main(argv, io) {
|
|
43607
44854
|
const [command, ...args] = argv;
|
|
@@ -43617,6 +44864,10 @@ async function main(argv, io) {
|
|
|
43617
44864
|
return await template(args, io);
|
|
43618
44865
|
case "suggest":
|
|
43619
44866
|
return await suggest(args, io);
|
|
44867
|
+
case "survey":
|
|
44868
|
+
return await survey2(args, io);
|
|
44869
|
+
case "junit-merge":
|
|
44870
|
+
return await junitMerge(args, io);
|
|
43620
44871
|
case "mcp":
|
|
43621
44872
|
return await mcp(args, io);
|
|
43622
44873
|
case "--version":
|
|
@@ -43649,11 +44900,11 @@ ${USAGE}`);
|
|
|
43649
44900
|
|
|
43650
44901
|
// src/cli/bin.ts
|
|
43651
44902
|
process.exitCode = await main(process.argv.slice(2), {
|
|
43652
|
-
out: (
|
|
43653
|
-
err: (
|
|
44903
|
+
out: (text2) => process.stdout.write(text2),
|
|
44904
|
+
err: (text2) => process.stderr.write(text2),
|
|
43654
44905
|
env: process.env,
|
|
43655
44906
|
artifact: import.meta.url,
|
|
43656
|
-
readStdin: () =>
|
|
44907
|
+
readStdin: () => readFileSync8(0),
|
|
43657
44908
|
stdin: process.stdin,
|
|
43658
44909
|
stdout: process.stdout
|
|
43659
44910
|
});
|