dunstan 0.1.13 → 0.1.15
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 +1700 -376
- 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.15";
|
|
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",
|
|
@@ -10519,11 +10567,11 @@ var FILES = { "handback-block-0.1.schema.json": `{
|
|
|
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 "listingPage": {\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 same read already held. In the read used, 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 "listing": {\n "description": "The read of the listing the selection is from, the last one, and the reads abandoned before it. A read is abandoned at its first page that repeats an earlier item, unless it is the third read, which is never abandoned.",\n "type": "object",\n "required": ["pageBound", "pages", "abandoned", "pullRequests", "duplicates", "end"],\n "additionalProperties": false,\n "properties": {\n "pageBound": { "type": "integer", "minimum": 1 },\n "pages": {\n "type": "array",\n "minItems": 1,\n "items": { "$ref": "#/$defs/listingPage" }\n },\n "abandoned": {\n "description": "One item per abandoned read, in order: its pages, the last of them the page that repeated an earlier item. No pull request list; abandoned reads contribute nothing to the selection. Empty when the first read was used.",\n "type": "array",\n "maxItems": 2,\n "items": {\n "type": "object",\n "required": ["pages"],\n "additionalProperties": false,\n "properties": {\n "pages": {\n "type": "array",\n "minItems": 1,\n "items": { "$ref": "#/$defs/listingPage" }\n }\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
|
}
|
|
@@ -12789,8 +12898,8 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
12789
12898
|
function stem(token) {
|
|
12790
12899
|
return token.length > 3 && token.endsWith("s") && !token.endsWith("ss") ? token.slice(0, -1) : token;
|
|
12791
12900
|
}
|
|
12792
|
-
function tokenize(
|
|
12793
|
-
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);
|
|
12794
12903
|
}
|
|
12795
12904
|
var Bm25Index = class {
|
|
12796
12905
|
termFrequencies;
|
|
@@ -12845,9 +12954,9 @@ function roundScore(score) {
|
|
|
12845
12954
|
function bestField(item, terms, index) {
|
|
12846
12955
|
let best = item.fields[0]?.[0] ?? "id";
|
|
12847
12956
|
let bestWeight = -1;
|
|
12848
|
-
for (const [name,
|
|
12957
|
+
for (const [name, text2] of item.fields) {
|
|
12849
12958
|
let weight = 0;
|
|
12850
|
-
for (const t of new Set(tokenize(
|
|
12959
|
+
for (const t of new Set(tokenize(text2))) if (terms.has(t)) weight += index.idf(t);
|
|
12851
12960
|
if (weight > bestWeight) {
|
|
12852
12961
|
best = name;
|
|
12853
12962
|
bestWeight = weight;
|
|
@@ -12856,7 +12965,7 @@ function bestField(item, terms, index) {
|
|
|
12856
12965
|
return best;
|
|
12857
12966
|
}
|
|
12858
12967
|
function itemTokens(item) {
|
|
12859
|
-
return item.fields.flatMap(([,
|
|
12968
|
+
return item.fields.flatMap(([, text2]) => tokenize(text2));
|
|
12860
12969
|
}
|
|
12861
12970
|
function compareRanked(a, b) {
|
|
12862
12971
|
const sa = a.candidate.score;
|
|
@@ -12980,17 +13089,17 @@ function ambiguousCandidates(combined, outcome) {
|
|
|
12980
13089
|
const tied = combined.filter((_, i) => scores[i] === top);
|
|
12981
13090
|
return tied.length >= 2 && new Set(tied.map(outcome)).size > 1;
|
|
12982
13091
|
}
|
|
12983
|
-
function namedWorkflowRuns(
|
|
13092
|
+
function namedWorkflowRuns(text2, combined, evidence) {
|
|
12984
13093
|
const section = evidence.checkRuns;
|
|
12985
13094
|
if (section?.status !== "ok") return [...combined];
|
|
12986
13095
|
const jobNames = [...new Set(section.runs.map((r) => r.name))];
|
|
12987
|
-
if (unheldSuffixJobs(
|
|
13096
|
+
if (unheldSuffixJobs(text2, jobNames).length > 0) return { reason: "no_matching_record_item" };
|
|
12988
13097
|
const recordedWorkflows = [
|
|
12989
13098
|
...new Set(
|
|
12990
13099
|
section.runs.flatMap((r) => [r.workflowPath, r.workflowName]).filter((w) => w !== void 0)
|
|
12991
13100
|
)
|
|
12992
13101
|
];
|
|
12993
|
-
const named = workflowJobs(
|
|
13102
|
+
const named = workflowJobs(text2, jobNames, recordedWorkflows);
|
|
12994
13103
|
if (named.length === 0) return [...combined];
|
|
12995
13104
|
const counted = new Map(
|
|
12996
13105
|
countedCheckRuns(section.runs, section.excludedIds).map((r) => [String(r.id), r])
|
|
@@ -13038,9 +13147,9 @@ function checkReaderClaim(claim2, evidence, repository) {
|
|
|
13038
13147
|
if (claim2.kind === "reference_closes") {
|
|
13039
13148
|
if (!ISSUE_REF2.test(declared)) return unverifiable2("no_comparable_record_field");
|
|
13040
13149
|
const head = evidence.pullRequest.headSha;
|
|
13041
|
-
const
|
|
13042
|
-
if (
|
|
13043
|
-
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);
|
|
13044
13153
|
}
|
|
13045
13154
|
const missing = sectionReason(type, evidence);
|
|
13046
13155
|
if (missing !== void 0) return unverifiable2(missing);
|
|
@@ -13269,10 +13378,10 @@ async function checkAt(input2, pr) {
|
|
|
13269
13378
|
const repository = pr.repository;
|
|
13270
13379
|
const ownRuns = await input2.afterPullRequest?.(pr) ?? [];
|
|
13271
13380
|
const report = await input2.report(pr);
|
|
13272
|
-
const
|
|
13273
|
-
const block = input2.block ?? recordBlock(extractHandbackBlock(
|
|
13381
|
+
const text2 = new TextDecoder("utf-8").decode(report.bytes);
|
|
13382
|
+
const block = input2.block ?? recordBlock(extractHandbackBlock(text2));
|
|
13274
13383
|
const found = block.status === "found" ? block.value : null;
|
|
13275
|
-
const proposed = input2.advisory === true ? extractClaims(
|
|
13384
|
+
const proposed = input2.advisory === true ? extractClaims(text2) : void 0;
|
|
13276
13385
|
const evidence = await input2.readers.evidence({
|
|
13277
13386
|
repository,
|
|
13278
13387
|
pullRequest: pr,
|
|
@@ -13368,6 +13477,102 @@ function noteText(note) {
|
|
|
13368
13477
|
return `${POSSIBLE_DISAGREEMENT}: ${shows}`;
|
|
13369
13478
|
}
|
|
13370
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
|
+
|
|
13371
13576
|
// src/cli/output.ts
|
|
13372
13577
|
var EXIT = { pass: 0, fail: 1, unverifiable: 2, error: 3 };
|
|
13373
13578
|
function exitFor(verdict) {
|
|
@@ -13382,8 +13587,8 @@ var UsageError = class extends Error {
|
|
|
13382
13587
|
var WIDTH = 48;
|
|
13383
13588
|
function cell(value) {
|
|
13384
13589
|
if (value === void 0) return "";
|
|
13385
|
-
const
|
|
13386
|
-
return
|
|
13590
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value);
|
|
13591
|
+
return text2.length > WIDTH ? `${text2.slice(0, WIDTH - 1)}\u2026` : text2;
|
|
13387
13592
|
}
|
|
13388
13593
|
function table(rows) {
|
|
13389
13594
|
const widths = rows[0]?.map((_, i) => Math.max(...rows.map((r) => (r[i] ?? "").length))) ?? [];
|
|
@@ -13391,7 +13596,7 @@ function table(rows) {
|
|
|
13391
13596
|
(r) => r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i] ?? 0)).join(" ").trimEnd()
|
|
13392
13597
|
).join("\n");
|
|
13393
13598
|
}
|
|
13394
|
-
function renderRecord(record2) {
|
|
13599
|
+
function renderRecord(record2, context) {
|
|
13395
13600
|
const p = record2.predicate;
|
|
13396
13601
|
const lines = [
|
|
13397
13602
|
`${p.checker.name} ${p.checker.version} ${p.subject.repository}#${p.subject.pullRequest} head ${p.subject.headSha}`,
|
|
@@ -13406,19 +13611,16 @@ function renderRecord(record2) {
|
|
|
13406
13611
|
lines.push(`block: invalid (${b.reason}: ${[...new Set(errors)].join("; ")})`);
|
|
13407
13612
|
} else lines.push(`block: missing (${b.reason})`);
|
|
13408
13613
|
if (p.claims.length > 0) {
|
|
13409
|
-
|
|
13410
|
-
"",
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
])
|
|
13420
|
-
])
|
|
13421
|
-
);
|
|
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
|
+
});
|
|
13422
13624
|
}
|
|
13423
13625
|
const unread2 = p.evidence.sources.filter((s) => s.error !== void 0);
|
|
13424
13626
|
if (unread2.length > 0) {
|
|
@@ -13537,7 +13739,7 @@ async function check(args, io) {
|
|
|
13537
13739
|
advisory: values.advisory === true
|
|
13538
13740
|
});
|
|
13539
13741
|
writeFileSync(out, serializeRecord(record2));
|
|
13540
|
-
io.out(renderRecord(record2));
|
|
13742
|
+
io.out(renderRecord(record2, { hasToken: client.hasToken }));
|
|
13541
13743
|
io.out(`record: ${out}
|
|
13542
13744
|
`);
|
|
13543
13745
|
if (assurance !== void 0) {
|
|
@@ -13547,6 +13749,95 @@ async function check(args, io) {
|
|
|
13547
13749
|
return exitFor(record2.predicate.verdict);
|
|
13548
13750
|
}
|
|
13549
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
|
+
|
|
13550
13841
|
// node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.0_zod@4.6.5/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
13551
13842
|
import process2 from "node:process";
|
|
13552
13843
|
|
|
@@ -14384,14 +14675,14 @@ function promiseAllObject(promisesObj) {
|
|
|
14384
14675
|
}
|
|
14385
14676
|
function randomString(length = 10) {
|
|
14386
14677
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
14387
|
-
let
|
|
14678
|
+
let str3 = "";
|
|
14388
14679
|
for (let i = 0; i < length; i++) {
|
|
14389
|
-
|
|
14680
|
+
str3 += chars[Math.floor(Math.random() * chars.length)];
|
|
14390
14681
|
}
|
|
14391
|
-
return
|
|
14682
|
+
return str3;
|
|
14392
14683
|
}
|
|
14393
|
-
function esc(
|
|
14394
|
-
return JSON.stringify(
|
|
14684
|
+
function esc(str3) {
|
|
14685
|
+
return JSON.stringify(str3);
|
|
14395
14686
|
}
|
|
14396
14687
|
function slugify(input2) {
|
|
14397
14688
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -14505,8 +14796,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
|
|
|
14505
14796
|
"symbol",
|
|
14506
14797
|
"undefined"
|
|
14507
14798
|
]);
|
|
14508
|
-
function escapeRegex(
|
|
14509
|
-
return
|
|
14799
|
+
function escapeRegex(str3) {
|
|
14800
|
+
return str3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14510
14801
|
}
|
|
14511
14802
|
function clone(inst, def, params) {
|
|
14512
14803
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
@@ -14760,13 +15051,13 @@ function getSizableOrigin(input2) {
|
|
|
14760
15051
|
return "unknown";
|
|
14761
15052
|
}
|
|
14762
15053
|
var highSurrogate = /[\uD800-\uDBFF]/;
|
|
14763
|
-
function codePointLength(
|
|
14764
|
-
const units =
|
|
14765
|
-
if (!highSurrogate.test(
|
|
15054
|
+
function codePointLength(str3) {
|
|
15055
|
+
const units = str3.length;
|
|
15056
|
+
if (!highSurrogate.test(str3))
|
|
14766
15057
|
return units;
|
|
14767
15058
|
let count = units;
|
|
14768
15059
|
for (let i = 0; i < units - 1; i++) {
|
|
14769
|
-
if ((
|
|
15060
|
+
if ((str3.charCodeAt(i) & 64512) === 55296 && (str3.charCodeAt(i + 1) & 64512) === 56320) {
|
|
14770
15061
|
count--;
|
|
14771
15062
|
i++;
|
|
14772
15063
|
}
|
|
@@ -14859,9 +15150,9 @@ var Class = class {
|
|
|
14859
15150
|
constructor(..._args) {
|
|
14860
15151
|
}
|
|
14861
15152
|
};
|
|
14862
|
-
function members(proto,
|
|
14863
|
-
for (const key in
|
|
14864
|
-
const desc = Object.getOwnPropertyDescriptor(
|
|
15153
|
+
function members(proto, table3) {
|
|
15154
|
+
for (const key in table3) {
|
|
15155
|
+
const desc = Object.getOwnPropertyDescriptor(table3, key);
|
|
14865
15156
|
if (desc.get)
|
|
14866
15157
|
Object.defineProperty(proto, key, { ...desc, enumerable: false });
|
|
14867
15158
|
else
|
|
@@ -14876,10 +15167,10 @@ function hide(inst, key, value) {
|
|
|
14876
15167
|
return own(inst, key, value, false);
|
|
14877
15168
|
}
|
|
14878
15169
|
// @__NO_SIDE_EFFECTS__
|
|
14879
|
-
function derived(computes,
|
|
15170
|
+
function derived(computes, table3) {
|
|
14880
15171
|
for (const key in computes) {
|
|
14881
15172
|
const compute = computes[key];
|
|
14882
|
-
Object.defineProperty(
|
|
15173
|
+
Object.defineProperty(table3, key, {
|
|
14883
15174
|
configurable: true,
|
|
14884
15175
|
enumerable: true,
|
|
14885
15176
|
get() {
|
|
@@ -14890,7 +15181,7 @@ function derived(computes, table2) {
|
|
|
14890
15181
|
}
|
|
14891
15182
|
});
|
|
14892
15183
|
}
|
|
14893
|
-
return
|
|
15184
|
+
return table3;
|
|
14894
15185
|
}
|
|
14895
15186
|
function defineBound(proto, key, fn) {
|
|
14896
15187
|
Object.defineProperty(proto, key, {
|
|
@@ -16122,8 +16413,8 @@ var Doc = class {
|
|
|
16122
16413
|
const lines = content.split("\n").filter((x) => x);
|
|
16123
16414
|
const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));
|
|
16124
16415
|
const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
|
|
16125
|
-
for (const
|
|
16126
|
-
this.content.push(
|
|
16416
|
+
for (const line2 of dedented) {
|
|
16417
|
+
this.content.push(line2);
|
|
16127
16418
|
}
|
|
16128
16419
|
}
|
|
16129
16420
|
compile() {
|
|
@@ -18576,7 +18867,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
18576
18867
|
var NONE = 0;
|
|
18577
18868
|
var ASSUMED = 1;
|
|
18578
18869
|
var PROVEN = 2;
|
|
18579
|
-
function isRecursive(inst, stack,
|
|
18870
|
+
function isRecursive(inst, stack, resolve5) {
|
|
18580
18871
|
const cached2 = recursive.get(inst);
|
|
18581
18872
|
if (cached2 !== void 0)
|
|
18582
18873
|
return cached2 ? PROVEN : NONE;
|
|
@@ -18586,7 +18877,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
18586
18877
|
let result = NONE;
|
|
18587
18878
|
const check3 = (child) => {
|
|
18588
18879
|
if (result !== PROVEN && child?._zod) {
|
|
18589
|
-
const answer = isRecursive(child, stack,
|
|
18880
|
+
const answer = isRecursive(child, stack, resolve5);
|
|
18590
18881
|
if (answer > result)
|
|
18591
18882
|
result = answer;
|
|
18592
18883
|
}
|
|
@@ -18597,7 +18888,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
18597
18888
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
18598
18889
|
if (spread && !desc.enumerable)
|
|
18599
18890
|
continue;
|
|
18600
|
-
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;
|
|
18601
18892
|
if (child > answer)
|
|
18602
18893
|
answer = child;
|
|
18603
18894
|
}
|
|
@@ -18661,7 +18952,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
18661
18952
|
break;
|
|
18662
18953
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
18663
18954
|
case "lazy": {
|
|
18664
|
-
const inner = def._cachedInner ?? (
|
|
18955
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
18665
18956
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
18666
18957
|
break;
|
|
18667
18958
|
}
|
|
@@ -22859,8 +23150,8 @@ function ko_default() {
|
|
|
22859
23150
|
}
|
|
22860
23151
|
|
|
22861
23152
|
// node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/lt.js
|
|
22862
|
-
var capitalizeFirstCharacter = (
|
|
22863
|
-
return
|
|
23153
|
+
var capitalizeFirstCharacter = (text2) => {
|
|
23154
|
+
return text2.charAt(0).toUpperCase() + text2.slice(1);
|
|
22864
23155
|
};
|
|
22865
23156
|
function getUnitTypeFromNumber(number4) {
|
|
22866
23157
|
const abs = Math.abs(number4);
|
|
@@ -24220,8 +24511,8 @@ var error43 = () => {
|
|
|
24220
24511
|
case "not_multiple_of":
|
|
24221
24512
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
24222
24513
|
case "unrecognized_keys": {
|
|
24223
|
-
const
|
|
24224
|
-
return `Chave${
|
|
24514
|
+
const plural4 = issue2.keys.length > 1 ? "s" : "";
|
|
24515
|
+
return `Chave${plural4} inv\xE1lida${plural4}: ${joinValues(issue2.keys, ", ")}`;
|
|
24225
24516
|
}
|
|
24226
24517
|
case "invalid_key":
|
|
24227
24518
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -24365,8 +24656,8 @@ var error44 = () => {
|
|
|
24365
24656
|
case "not_multiple_of":
|
|
24366
24657
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
24367
24658
|
case "unrecognized_keys": {
|
|
24368
|
-
const
|
|
24369
|
-
return `Chave${
|
|
24659
|
+
const plural4 = issue2.keys.length > 1 ? "s" : "";
|
|
24660
|
+
return `Chave${plural4} inv\xE1lida${plural4}: ${joinValues(issue2.keys, ", ")}`;
|
|
24370
24661
|
}
|
|
24371
24662
|
case "invalid_key":
|
|
24372
24663
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -34771,16 +35062,16 @@ var ReadBuffer = class {
|
|
|
34771
35062
|
if (index === -1) {
|
|
34772
35063
|
return null;
|
|
34773
35064
|
}
|
|
34774
|
-
const
|
|
35065
|
+
const line2 = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
|
|
34775
35066
|
this._buffer = this._buffer.subarray(index + 1);
|
|
34776
|
-
return deserializeMessage(
|
|
35067
|
+
return deserializeMessage(line2);
|
|
34777
35068
|
}
|
|
34778
35069
|
clear() {
|
|
34779
35070
|
this._buffer = void 0;
|
|
34780
35071
|
}
|
|
34781
35072
|
};
|
|
34782
|
-
function deserializeMessage(
|
|
34783
|
-
return JSONRPCMessageSchema.parse(JSON.parse(
|
|
35073
|
+
function deserializeMessage(line2) {
|
|
35074
|
+
return JSONRPCMessageSchema.parse(JSON.parse(line2));
|
|
34784
35075
|
}
|
|
34785
35076
|
function serializeMessage(message) {
|
|
34786
35077
|
return JSON.stringify(message) + "\n";
|
|
@@ -34842,20 +35133,20 @@ var StdioServerTransport = class {
|
|
|
34842
35133
|
this.onclose?.();
|
|
34843
35134
|
}
|
|
34844
35135
|
send(message) {
|
|
34845
|
-
return new Promise((
|
|
35136
|
+
return new Promise((resolve5) => {
|
|
34846
35137
|
const json2 = serializeMessage(message);
|
|
34847
35138
|
if (this._stdout.write(json2)) {
|
|
34848
|
-
|
|
35139
|
+
resolve5();
|
|
34849
35140
|
} else {
|
|
34850
|
-
this._stdout.once("drain",
|
|
35141
|
+
this._stdout.once("drain", resolve5);
|
|
34851
35142
|
}
|
|
34852
35143
|
});
|
|
34853
35144
|
}
|
|
34854
35145
|
};
|
|
34855
35146
|
|
|
34856
35147
|
// src/mcp/server.ts
|
|
34857
|
-
import { existsSync, writeFileSync as
|
|
34858
|
-
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";
|
|
34859
35150
|
|
|
34860
35151
|
// node_modules/.pnpm/zod@4.6.5/node_modules/zod/v3/helpers/util.js
|
|
34861
35152
|
var util;
|
|
@@ -40831,7 +41122,7 @@ var Protocol = class {
|
|
|
40831
41122
|
return;
|
|
40832
41123
|
}
|
|
40833
41124
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
40834
|
-
await new Promise((
|
|
41125
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
|
|
40835
41126
|
options?.signal?.throwIfAborted();
|
|
40836
41127
|
}
|
|
40837
41128
|
} catch (error62) {
|
|
@@ -40848,7 +41139,7 @@ var Protocol = class {
|
|
|
40848
41139
|
*/
|
|
40849
41140
|
request(request, resultSchema, options) {
|
|
40850
41141
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
40851
|
-
return new Promise((
|
|
41142
|
+
return new Promise((resolve5, reject) => {
|
|
40852
41143
|
const earlyReject = (error62) => {
|
|
40853
41144
|
reject(error62);
|
|
40854
41145
|
};
|
|
@@ -40926,7 +41217,7 @@ var Protocol = class {
|
|
|
40926
41217
|
if (!parseResult.success) {
|
|
40927
41218
|
reject(parseResult.error);
|
|
40928
41219
|
} else {
|
|
40929
|
-
|
|
41220
|
+
resolve5(parseResult.data);
|
|
40930
41221
|
}
|
|
40931
41222
|
} catch (error62) {
|
|
40932
41223
|
reject(error62);
|
|
@@ -41180,20 +41471,20 @@ var Protocol = class {
|
|
|
41180
41471
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
41181
41472
|
*/
|
|
41182
41473
|
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
41183
|
-
let
|
|
41474
|
+
let interval2 = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
41184
41475
|
try {
|
|
41185
41476
|
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
41186
41477
|
if (task?.pollInterval) {
|
|
41187
|
-
|
|
41478
|
+
interval2 = task.pollInterval;
|
|
41188
41479
|
}
|
|
41189
41480
|
} catch {
|
|
41190
41481
|
}
|
|
41191
|
-
return new Promise((
|
|
41482
|
+
return new Promise((resolve5, reject) => {
|
|
41192
41483
|
if (signal.aborted) {
|
|
41193
41484
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
41194
41485
|
return;
|
|
41195
41486
|
}
|
|
41196
|
-
const timeoutId = setTimeout(
|
|
41487
|
+
const timeoutId = setTimeout(resolve5, interval2);
|
|
41197
41488
|
signal.addEventListener("abort", () => {
|
|
41198
41489
|
clearTimeout(timeoutId);
|
|
41199
41490
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -42324,7 +42615,7 @@ var McpServer = class {
|
|
|
42324
42615
|
let task = createTaskResult.task;
|
|
42325
42616
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
42326
42617
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
42327
|
-
await new Promise((
|
|
42618
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
|
|
42328
42619
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
42329
42620
|
if (!updatedTask) {
|
|
42330
42621
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -42944,13 +43235,13 @@ function sentenceAt(report, span) {
|
|
|
42944
43235
|
const lines = report.split("\n");
|
|
42945
43236
|
const starts = [];
|
|
42946
43237
|
let offset = 0;
|
|
42947
|
-
for (const
|
|
43238
|
+
for (const line2 of lines) {
|
|
42948
43239
|
starts.push(offset);
|
|
42949
|
-
offset +=
|
|
43240
|
+
offset += line2.length + 1;
|
|
42950
43241
|
}
|
|
42951
|
-
const lineOf = (
|
|
43242
|
+
const lineOf = (at2) => {
|
|
42952
43243
|
let i = 0;
|
|
42953
|
-
while (i + 1 < starts.length && starts[i + 1] <=
|
|
43244
|
+
while (i + 1 < starts.length && starts[i + 1] <= at2) i++;
|
|
42954
43245
|
return i;
|
|
42955
43246
|
};
|
|
42956
43247
|
const blank = (i) => (lines[i] ?? "").trim() === "";
|
|
@@ -42973,8 +43264,8 @@ function sentenceAt(report, span) {
|
|
|
42973
43264
|
break;
|
|
42974
43265
|
}
|
|
42975
43266
|
}
|
|
42976
|
-
const
|
|
42977
|
-
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;
|
|
42978
43269
|
}
|
|
42979
43270
|
function suggestDeclarations(report) {
|
|
42980
43271
|
const block = extractHandbackBlock(report);
|
|
@@ -43053,19 +43344,19 @@ var INSTRUCTIONS = `Before you say a task is done: generate your dunstan-handbac
|
|
|
43053
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\`.`;
|
|
43054
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.`;
|
|
43055
43346
|
function reportOfBlock(block) {
|
|
43056
|
-
const
|
|
43347
|
+
const text2 = canonicalize(block);
|
|
43057
43348
|
return {
|
|
43058
43349
|
report: {
|
|
43059
|
-
bytes: new TextEncoder().encode(
|
|
43350
|
+
bytes: new TextEncoder().encode(text2),
|
|
43060
43351
|
source: { kind: "api", locator: `mcp ${TOOL_NAME}#block` }
|
|
43061
43352
|
},
|
|
43062
|
-
block: recordBlock(readBlockContent(
|
|
43353
|
+
block: recordBlock(readBlockContent(text2))
|
|
43063
43354
|
};
|
|
43064
43355
|
}
|
|
43065
|
-
function reportOfText(
|
|
43356
|
+
function reportOfText(text2) {
|
|
43066
43357
|
return {
|
|
43067
43358
|
report: {
|
|
43068
|
-
bytes: new TextEncoder().encode(
|
|
43359
|
+
bytes: new TextEncoder().encode(text2),
|
|
43069
43360
|
source: { kind: "api", locator: `mcp ${TOOL_NAME}#report` }
|
|
43070
43361
|
}
|
|
43071
43362
|
};
|
|
@@ -43076,7 +43367,7 @@ async function checkHandback(input2, options) {
|
|
|
43076
43367
|
"pass exactly one of block (the handback block as a JSON object) or report (text carrying one dunstan-handback fence)"
|
|
43077
43368
|
);
|
|
43078
43369
|
}
|
|
43079
|
-
const outPath = input2.outPath === void 0 ? void 0 :
|
|
43370
|
+
const outPath = input2.outPath === void 0 ? void 0 : resolve3(input2.outPath);
|
|
43080
43371
|
if (outPath !== void 0 && existsSync(outPath)) {
|
|
43081
43372
|
throw new InputError(`${outPath} exists; the tool never overwrites a file`);
|
|
43082
43373
|
}
|
|
@@ -43108,10 +43399,10 @@ async function checkHandback(input2, options) {
|
|
|
43108
43399
|
recordFile: basename2(recordFile)
|
|
43109
43400
|
});
|
|
43110
43401
|
const serialized = serializeRecord(record2);
|
|
43111
|
-
if (outPath !== void 0)
|
|
43112
|
-
return handbackResult(record2, outPath, notes);
|
|
43402
|
+
if (outPath !== void 0) writeFileSync3(outPath, serialized, { flag: "wx" });
|
|
43403
|
+
return handbackResult(record2, outPath, notes, { hasToken: client.hasToken });
|
|
43113
43404
|
}
|
|
43114
|
-
function handbackResult(record2, outPath, notes) {
|
|
43405
|
+
function handbackResult(record2, outPath, notes, context) {
|
|
43115
43406
|
const verdict = record2.predicate.verdict;
|
|
43116
43407
|
const head = [`verdict: ${verdict}`];
|
|
43117
43408
|
if (verdict !== "pass") {
|
|
@@ -43125,7 +43416,7 @@ function handbackResult(record2, outPath, notes) {
|
|
|
43125
43416
|
content: [
|
|
43126
43417
|
{ type: "text", text: `${head.join("\n")}
|
|
43127
43418
|
` },
|
|
43128
|
-
{ type: "text", text: renderRecord(record2) },
|
|
43419
|
+
{ type: "text", text: renderRecord(record2, context) },
|
|
43129
43420
|
{ type: "text", text: serializeRecord(record2) }
|
|
43130
43421
|
]
|
|
43131
43422
|
};
|
|
@@ -43211,8 +43502,8 @@ async function mcp(args, io) {
|
|
|
43211
43502
|
warn: io.err
|
|
43212
43503
|
});
|
|
43213
43504
|
const stdin = io.stdin ?? process.stdin;
|
|
43214
|
-
const closed = new Promise((
|
|
43215
|
-
server.server.onclose =
|
|
43505
|
+
const closed = new Promise((resolve5) => {
|
|
43506
|
+
server.server.onclose = resolve5;
|
|
43216
43507
|
});
|
|
43217
43508
|
stdin.once("end", () => void server.close());
|
|
43218
43509
|
await server.connect(new StdioServerTransport(stdin, io.stdout ?? process.stdout));
|
|
@@ -43221,9 +43512,9 @@ async function mcp(args, io) {
|
|
|
43221
43512
|
}
|
|
43222
43513
|
|
|
43223
43514
|
// src/cli/rerun.ts
|
|
43224
|
-
import { existsSync as existsSync2, readFileSync as
|
|
43225
|
-
import { basename as basename3, resolve as
|
|
43226
|
-
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";
|
|
43227
43518
|
function diffPointers(a, b, pointer = "") {
|
|
43228
43519
|
const isObject2 = (v) => v !== null && typeof v === "object";
|
|
43229
43520
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
@@ -43245,7 +43536,7 @@ function defaultOut(path) {
|
|
|
43245
43536
|
return path.endsWith(".json") ? `${path.slice(0, -5)}.rerun.json` : `${path}.rerun.json`;
|
|
43246
43537
|
}
|
|
43247
43538
|
async function rerun(args, io) {
|
|
43248
|
-
const { values, positionals } =
|
|
43539
|
+
const { values, positionals } = parseArgs3({
|
|
43249
43540
|
args,
|
|
43250
43541
|
strict: true,
|
|
43251
43542
|
allowPositionals: true,
|
|
@@ -43254,9 +43545,9 @@ async function rerun(args, io) {
|
|
|
43254
43545
|
if (positionals.length !== 1) throw new UsageError("dunstan rerun <record.json> [--out <path>]");
|
|
43255
43546
|
const path = positionals[0];
|
|
43256
43547
|
const out = values.out ?? defaultOut(path);
|
|
43257
|
-
if (
|
|
43548
|
+
if (resolve4(out) === resolve4(path))
|
|
43258
43549
|
throw new UsageError("rerun never overwrites the record it re-runs");
|
|
43259
|
-
const value = parseStrictJson(
|
|
43550
|
+
const value = parseStrictJson(readFileSync5(path, "utf8"));
|
|
43260
43551
|
const errors = validateRecord(value);
|
|
43261
43552
|
if (errors.length > 0) {
|
|
43262
43553
|
throw new UsageError(
|
|
@@ -43294,7 +43585,7 @@ async function rerun(args, io) {
|
|
|
43294
43585
|
});
|
|
43295
43586
|
if (existsSync2(out)) io.err(`dunstan: replacing ${out}
|
|
43296
43587
|
`);
|
|
43297
|
-
|
|
43588
|
+
writeFileSync4(out, serializeRecord(record2));
|
|
43298
43589
|
const p = record2.predicate;
|
|
43299
43590
|
if (p.digests.evidence !== old.digests.evidence) {
|
|
43300
43591
|
const fields = diffPointers(
|
|
@@ -43313,17 +43604,17 @@ async function rerun(args, io) {
|
|
|
43313
43604
|
|
|
43314
43605
|
`);
|
|
43315
43606
|
}
|
|
43316
|
-
io.out(renderRecord(record2));
|
|
43607
|
+
io.out(renderRecord(record2, { hasToken: client.hasToken }));
|
|
43317
43608
|
io.out(`record: ${out} (the original ${path} is unchanged)
|
|
43318
43609
|
`);
|
|
43319
43610
|
return exitFor(p.verdict);
|
|
43320
43611
|
}
|
|
43321
43612
|
|
|
43322
43613
|
// src/cli/suggest.ts
|
|
43323
|
-
import { readFileSync as
|
|
43324
|
-
import { parseArgs as
|
|
43614
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
43615
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
43325
43616
|
async function suggest(args, io) {
|
|
43326
|
-
const { values } =
|
|
43617
|
+
const { values } = parseArgs4({
|
|
43327
43618
|
args,
|
|
43328
43619
|
strict: true,
|
|
43329
43620
|
allowPositionals: false,
|
|
@@ -43332,7 +43623,7 @@ async function suggest(args, io) {
|
|
|
43332
43623
|
const file2 = values["report-file"];
|
|
43333
43624
|
let bytes;
|
|
43334
43625
|
try {
|
|
43335
|
-
bytes = file2 === void 0 || file2 === "-" ? io.readStdin() :
|
|
43626
|
+
bytes = file2 === void 0 || file2 === "-" ? io.readStdin() : readFileSync6(file2);
|
|
43336
43627
|
} catch (e) {
|
|
43337
43628
|
io.err(`dunstan: cannot read the report ${file2 ?? "stdin"}: ${e.message}
|
|
43338
43629
|
`);
|
|
@@ -43342,61 +43633,833 @@ async function suggest(args, io) {
|
|
|
43342
43633
|
return 0;
|
|
43343
43634
|
}
|
|
43344
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 LISTING_READS = 3;
|
|
43783
|
+
var DEFAULT_LAST = 30;
|
|
43784
|
+
var MAX_LAST = 200;
|
|
43785
|
+
function appendListed(listed2, seen, items) {
|
|
43786
|
+
let duplicates = 0;
|
|
43787
|
+
for (const item of items) {
|
|
43788
|
+
if (seen.has(item.number)) duplicates++;
|
|
43789
|
+
else {
|
|
43790
|
+
seen.add(item.number);
|
|
43791
|
+
listed2.push(item);
|
|
43792
|
+
}
|
|
43793
|
+
}
|
|
43794
|
+
return duplicates;
|
|
43795
|
+
}
|
|
43796
|
+
var sinceStart = (since) => `${since}T00:00:00Z`;
|
|
43797
|
+
function selectPullRequests(parameters, listed2) {
|
|
43798
|
+
const authors = new Set(parameters.authors.map((a) => a.toLowerCase()));
|
|
43799
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43800
|
+
return listed2.filter((pr) => {
|
|
43801
|
+
if (seen.has(pr.number)) return false;
|
|
43802
|
+
seen.add(pr.number);
|
|
43803
|
+
return true;
|
|
43804
|
+
}).filter((pr) => parameters.since === null || pr.createdAt >= sinceStart(parameters.since)).filter(
|
|
43805
|
+
(pr) => authors.size === 0 || pr.author !== null && authors.has(pr.author.toLowerCase())
|
|
43806
|
+
).slice(0, parameters.last).map((pr) => pr.number);
|
|
43807
|
+
}
|
|
43808
|
+
function listingStop(parameters, pages, listed2) {
|
|
43809
|
+
const last = pages.at(-1);
|
|
43810
|
+
if (last === void 0) return null;
|
|
43811
|
+
if (last.error !== void 0) return "listing_unreadable";
|
|
43812
|
+
if (selectPullRequests(parameters, listed2).length >= parameters.last) return "selection_full";
|
|
43813
|
+
const since = parameters.since;
|
|
43814
|
+
if (since !== null && listed2.some((pr) => pr.createdAt < sinceStart(since))) {
|
|
43815
|
+
return "since_reached";
|
|
43816
|
+
}
|
|
43817
|
+
if (last.items < LISTING_PER_PAGE) return "listing_ended";
|
|
43818
|
+
return null;
|
|
43819
|
+
}
|
|
43820
|
+
function surveyCommand(parameters, out) {
|
|
43821
|
+
const words2 = [
|
|
43822
|
+
"dunstan",
|
|
43823
|
+
"survey",
|
|
43824
|
+
"--repo",
|
|
43825
|
+
parameters.repository,
|
|
43826
|
+
"--last",
|
|
43827
|
+
`${parameters.last}`
|
|
43828
|
+
];
|
|
43829
|
+
if (parameters.since !== null) words2.push("--since", parameters.since);
|
|
43830
|
+
for (const author of parameters.authors) words2.push("--author", author);
|
|
43831
|
+
if (parameters.report.kind === "pr-comment") {
|
|
43832
|
+
words2.push("--report-comment-author", parameters.report.author);
|
|
43833
|
+
}
|
|
43834
|
+
if (parameters.advisory) words2.push("--advisory");
|
|
43835
|
+
words2.push("--out", out);
|
|
43836
|
+
return words2.join(" ");
|
|
43837
|
+
}
|
|
43838
|
+
function surveyRerun(parameters, out) {
|
|
43839
|
+
return { offline: `dunstan verify ${out}`, online: surveyCommand(parameters, out) };
|
|
43840
|
+
}
|
|
43841
|
+
function surveyDigest(record2) {
|
|
43842
|
+
const { digest: _, ...rest } = record2;
|
|
43843
|
+
return sha256Canonical(rest);
|
|
43844
|
+
}
|
|
43845
|
+
function buildSurveyRecord(input2) {
|
|
43846
|
+
const unsigned = {
|
|
43847
|
+
format: SURVEY_FORMAT,
|
|
43848
|
+
checker: input2.checker,
|
|
43849
|
+
selection: {
|
|
43850
|
+
parameters: input2.parameters,
|
|
43851
|
+
reader: { authenticated: input2.reader.authenticated },
|
|
43852
|
+
listing: input2.listing,
|
|
43853
|
+
selected: selectPullRequests(input2.parameters, input2.listing.pullRequests)
|
|
43854
|
+
},
|
|
43855
|
+
entries: input2.entries,
|
|
43856
|
+
aggregate: aggregate(
|
|
43857
|
+
input2.entries,
|
|
43858
|
+
input2.listing.pullRequests.length,
|
|
43859
|
+
input2.parameters.advisory,
|
|
43860
|
+
input2.reader
|
|
43861
|
+
),
|
|
43862
|
+
rerun: surveyRerun(input2.parameters, input2.out)
|
|
43863
|
+
};
|
|
43864
|
+
return { ...unsigned, digest: { sha256: surveyDigest(unsigned) } };
|
|
43865
|
+
}
|
|
43866
|
+
function serializeSurvey(record2) {
|
|
43867
|
+
return `${JSON.stringify(record2, null, 2)}
|
|
43868
|
+
`;
|
|
43869
|
+
}
|
|
43870
|
+
function isSurveyRecord(value) {
|
|
43871
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && value.format === SURVEY_FORMAT;
|
|
43872
|
+
}
|
|
43873
|
+
|
|
43874
|
+
// src/pipeline/survey.ts
|
|
43875
|
+
var recordFileName = (repository, number4) => `dunstan-${repository.replace("/", "-")}-${number4}.json`;
|
|
43876
|
+
function rateLimited(response) {
|
|
43877
|
+
const s = response.status;
|
|
43878
|
+
return s === 429 || s === 403 && (response.headers.get("x-ratelimit-remaining") === "0" || response.headers.get("retry-after") !== null);
|
|
43879
|
+
}
|
|
43880
|
+
function str2(value) {
|
|
43881
|
+
return typeof value === "string" ? value : null;
|
|
43882
|
+
}
|
|
43883
|
+
function listedOf(item) {
|
|
43884
|
+
if (item === null || typeof item !== "object") return null;
|
|
43885
|
+
const pr = item;
|
|
43886
|
+
const createdAt = str2(pr.created_at);
|
|
43887
|
+
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)) {
|
|
43888
|
+
return null;
|
|
43889
|
+
}
|
|
43890
|
+
const user = pr.user;
|
|
43891
|
+
const login = str2(user?.login);
|
|
43892
|
+
return {
|
|
43893
|
+
number: pr.number,
|
|
43894
|
+
createdAt,
|
|
43895
|
+
author: login === "" ? null : login,
|
|
43896
|
+
merged: pr.merged_at !== null && pr.merged_at !== void 0
|
|
43897
|
+
};
|
|
43898
|
+
}
|
|
43899
|
+
async function readListing(client, parameters, now, pageBound) {
|
|
43900
|
+
const abandoned = [];
|
|
43901
|
+
for (; ; ) {
|
|
43902
|
+
const last = abandoned.length === LISTING_READS - 1;
|
|
43903
|
+
const read = await readOnce(client, parameters, now, pageBound, !last);
|
|
43904
|
+
if ("end" in read) return { ...read, abandoned };
|
|
43905
|
+
abandoned.push(read);
|
|
43906
|
+
}
|
|
43907
|
+
}
|
|
43908
|
+
async function readOnce(client, parameters, now, pageBound, abandonOnRepeat) {
|
|
43909
|
+
const pages = [];
|
|
43910
|
+
const pullRequests = [];
|
|
43911
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43912
|
+
const done = (end) => ({
|
|
43913
|
+
pageBound,
|
|
43914
|
+
pages,
|
|
43915
|
+
pullRequests,
|
|
43916
|
+
duplicates: pages.reduce((sum, p) => sum + p.duplicates, 0),
|
|
43917
|
+
end
|
|
43918
|
+
});
|
|
43919
|
+
for (let page = 1; page <= pageBound; page++) {
|
|
43920
|
+
const path = `/repos/${parameters.repository}/pulls?state=closed&sort=created&direction=desc&per_page=${LISTING_PER_PAGE}&page=${page}`;
|
|
43921
|
+
const read = await client.read("pull_request", path, { record: false });
|
|
43922
|
+
const entry = {
|
|
43923
|
+
page,
|
|
43924
|
+
locator: `GET ${path}`,
|
|
43925
|
+
sha256: sha256Hex(read.body),
|
|
43926
|
+
readAt: now().toISOString(),
|
|
43927
|
+
items: 0,
|
|
43928
|
+
duplicates: 0
|
|
43929
|
+
};
|
|
43930
|
+
if (!read.ok) {
|
|
43931
|
+
entry.error = read.error;
|
|
43932
|
+
} else {
|
|
43933
|
+
const items = Array.isArray(read.json) ? read.json.map(listedOf) : null;
|
|
43934
|
+
if (items === null || items.some((i) => i === null)) entry.error = "parse";
|
|
43935
|
+
else {
|
|
43936
|
+
entry.items = items.length;
|
|
43937
|
+
entry.duplicates = appendListed(pullRequests, seen, items);
|
|
43938
|
+
}
|
|
43939
|
+
}
|
|
43940
|
+
pages.push(entry);
|
|
43941
|
+
if (abandonOnRepeat && entry.duplicates > 0) return { pages };
|
|
43942
|
+
const end = listingStop(parameters, pages, pullRequests);
|
|
43943
|
+
if (end !== null) return done(end);
|
|
43944
|
+
}
|
|
43945
|
+
return done("page_bound");
|
|
43946
|
+
}
|
|
43947
|
+
async function survey(input2) {
|
|
43948
|
+
const { parameters } = input2;
|
|
43949
|
+
const now = input2.now ?? (() => /* @__PURE__ */ new Date());
|
|
43950
|
+
const limited = /* @__PURE__ */ new Set();
|
|
43951
|
+
const baseFetch = input2.fetch ?? fetch;
|
|
43952
|
+
const watched = (async (resource, init) => {
|
|
43953
|
+
const response = await baseFetch(resource, init);
|
|
43954
|
+
if (rateLimited(response)) {
|
|
43955
|
+
const url2 = typeof resource === "string" ? resource : resource instanceof URL ? resource.href : resource.url;
|
|
43956
|
+
limited.add(new URL(url2).pathname);
|
|
43957
|
+
}
|
|
43958
|
+
return response;
|
|
43959
|
+
});
|
|
43960
|
+
const newClient = () => new GitHubClient({
|
|
43961
|
+
token: input2.token,
|
|
43962
|
+
fetch: watched,
|
|
43963
|
+
now,
|
|
43964
|
+
...input2.sleep === void 0 ? {} : { sleep: input2.sleep },
|
|
43965
|
+
...input2.warn === void 0 ? {} : { warn: input2.warn }
|
|
43966
|
+
});
|
|
43967
|
+
const listingClient = newClient();
|
|
43968
|
+
const reader = { authenticated: listingClient.hasToken };
|
|
43969
|
+
const listing = await readListing(listingClient, parameters, now, input2.pageBound ?? MAX_PAGES);
|
|
43970
|
+
const entries = [];
|
|
43971
|
+
for (const number4 of selectPullRequests(parameters, listing.pullRequests)) {
|
|
43972
|
+
const client = newClient();
|
|
43973
|
+
const repository = parameters.repository;
|
|
43974
|
+
const pullPath = `/repos/${repository}/pulls/${number4}`;
|
|
43975
|
+
const commentsPath = `/repos/${repository}/issues/${number4}/comments`;
|
|
43976
|
+
const report = parameters.report;
|
|
43977
|
+
try {
|
|
43978
|
+
const record2 = await checkPullRequest({
|
|
43979
|
+
readers: githubReaders(client),
|
|
43980
|
+
repository,
|
|
43981
|
+
number: number4,
|
|
43982
|
+
report: async (pr) => report.kind === "pr-body" ? reportFromPullRequestBody(pr.repository, pr) : reportFromComment(client, pr.repository, number4, report.author),
|
|
43983
|
+
checker: input2.checker,
|
|
43984
|
+
recordFile: recordFileName(repository, number4),
|
|
43985
|
+
advisory: parameters.advisory
|
|
43986
|
+
});
|
|
43987
|
+
entries.push({ pullRequest: number4, record: record2 });
|
|
43988
|
+
} catch (e) {
|
|
43989
|
+
let reason;
|
|
43990
|
+
if (e instanceof PullRequestMoved) reason = "pull_request_moved";
|
|
43991
|
+
else if (e instanceof PullRequestUnreadable) {
|
|
43992
|
+
reason = limited.has(pullPath) ? "rate_limited" : "pull_request_unreadable";
|
|
43993
|
+
} else if (e instanceof ReportUnavailable) {
|
|
43994
|
+
reason = limited.has(commentsPath) ? "rate_limited" : "report_unavailable";
|
|
43995
|
+
} else throw e;
|
|
43996
|
+
entries.push({ pullRequest: number4, record: null, reason });
|
|
43997
|
+
}
|
|
43998
|
+
}
|
|
43999
|
+
return buildSurveyRecord({
|
|
44000
|
+
checker: input2.checker,
|
|
44001
|
+
parameters,
|
|
44002
|
+
reader,
|
|
44003
|
+
listing,
|
|
44004
|
+
entries,
|
|
44005
|
+
out: input2.out
|
|
44006
|
+
});
|
|
44007
|
+
}
|
|
44008
|
+
|
|
44009
|
+
// src/survey/summary.ts
|
|
44010
|
+
var COLUMN_LABEL = {
|
|
44011
|
+
supported: "Supported",
|
|
44012
|
+
unverifiable: "Unverifiable",
|
|
44013
|
+
notRecordCheckable: "Not record-checkable",
|
|
44014
|
+
contradicted: "Contradicted"
|
|
44015
|
+
};
|
|
44016
|
+
var END_TEXT = {
|
|
44017
|
+
selection_full: "it held enough pull requests for the selection",
|
|
44018
|
+
since_reached: "it reached pull requests created before the --since date",
|
|
44019
|
+
listing_ended: "it ended",
|
|
44020
|
+
listing_unreadable: "a page could not be read; the selection is from the pages read before it",
|
|
44021
|
+
page_bound: "it reached the page bound"
|
|
44022
|
+
};
|
|
44023
|
+
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.";
|
|
44024
|
+
function row(cells) {
|
|
44025
|
+
return `| ${cells.join(" | ")} |`;
|
|
44026
|
+
}
|
|
44027
|
+
function table2(head, rows) {
|
|
44028
|
+
return [row(head), row(head.map(() => "---")), ...rows.map(row)];
|
|
44029
|
+
}
|
|
44030
|
+
function share(r) {
|
|
44031
|
+
return r.value ?? "n/a";
|
|
44032
|
+
}
|
|
44033
|
+
function interval(r) {
|
|
44034
|
+
return r.wilson95 === null ? "n/a" : `${r.wilson95.low}\u2013${r.wilson95.high}`;
|
|
44035
|
+
}
|
|
44036
|
+
function plural2(n, one, many) {
|
|
44037
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
44038
|
+
}
|
|
44039
|
+
function renderSurveySummary(record2) {
|
|
44040
|
+
const { parameters, listing, selected } = record2.selection;
|
|
44041
|
+
const a = record2.aggregate;
|
|
44042
|
+
const lines = [];
|
|
44043
|
+
const scope = [`the ${parameters.last} most recently created closed pull requests`];
|
|
44044
|
+
if (parameters.since !== null) scope.push(`created on or after ${parameters.since}`);
|
|
44045
|
+
if (parameters.authors.length > 0) scope.push(`opened by ${parameters.authors.join(", ")}`);
|
|
44046
|
+
const report = parameters.report.kind === "pr-body" ? "the pull request body" : `the latest comment by ${parameters.report.author}`;
|
|
44047
|
+
lines.push(
|
|
44048
|
+
`# Settleability survey: ${parameters.repository}`,
|
|
44049
|
+
"",
|
|
44050
|
+
`DRAFT (${record2.format}). ${record2.checker.name} ${record2.checker.version}, survey mapping ${a.mapping.version}. Read-only.`,
|
|
44051
|
+
"",
|
|
44052
|
+
`Selection: ${scope.join(", ")}. Report: ${report}. Advisory reader: ${parameters.advisory ? "on" : "off"}.`,
|
|
44053
|
+
"",
|
|
44054
|
+
record2.selection.reader.authenticated ? "GitHub was read with a token (GITHUB_TOKEN set)." : `GitHub was read without a token (GITHUB_TOKEN not set). ${ANONYMOUS}`,
|
|
44055
|
+
"",
|
|
44056
|
+
"This survey reports which claims the record could settle. A claim the record could not settle is neither supported nor contradicted.",
|
|
44057
|
+
"",
|
|
44058
|
+
"## Claims the record could not settle",
|
|
44059
|
+
"",
|
|
44060
|
+
`The record could not settle ${a.cannotSettle.numerator} of ${plural2(a.cannotSettle.denominator, "claim", "claims")}.`,
|
|
44061
|
+
""
|
|
44062
|
+
);
|
|
44063
|
+
if (a.unsettled.length === 0) {
|
|
44064
|
+
lines.push("None.");
|
|
44065
|
+
} else {
|
|
44066
|
+
lines.push(
|
|
44067
|
+
...table2(
|
|
44068
|
+
["Reason", "Column", "Claims", "Remedy"],
|
|
44069
|
+
a.unsettled.map((u) => [
|
|
44070
|
+
`\`${u.reason}\``,
|
|
44071
|
+
COLUMN_LABEL[u.column],
|
|
44072
|
+
u.count,
|
|
44073
|
+
u.remedy ?? "none listed"
|
|
44074
|
+
])
|
|
44075
|
+
)
|
|
44076
|
+
);
|
|
44077
|
+
}
|
|
44078
|
+
lines.push(
|
|
44079
|
+
"",
|
|
44080
|
+
"## Results",
|
|
44081
|
+
"",
|
|
44082
|
+
...table2(
|
|
44083
|
+
["Measure", "Claims", "Of", "Share", "Wilson 95%"],
|
|
44084
|
+
[
|
|
44085
|
+
[
|
|
44086
|
+
"The record could not settle",
|
|
44087
|
+
a.cannotSettle.numerator,
|
|
44088
|
+
`${a.cannotSettle.denominator} claims`,
|
|
44089
|
+
share(a.cannotSettle),
|
|
44090
|
+
interval(a.cannotSettle)
|
|
44091
|
+
],
|
|
44092
|
+
[
|
|
44093
|
+
"Contradicted by the record, of the claims it settled",
|
|
44094
|
+
a.contradictedOfSettled.numerator,
|
|
44095
|
+
`${a.contradictedOfSettled.denominator} settled claims`,
|
|
44096
|
+
share(a.contradictedOfSettled),
|
|
44097
|
+
interval(a.contradictedOfSettled)
|
|
44098
|
+
]
|
|
44099
|
+
]
|
|
44100
|
+
),
|
|
44101
|
+
"",
|
|
44102
|
+
"## By claim type",
|
|
44103
|
+
"",
|
|
44104
|
+
...table2(
|
|
44105
|
+
["Type", "Supported", "Unverifiable", "Not record-checkable", "Contradicted"],
|
|
44106
|
+
[
|
|
44107
|
+
...a.byType.map((t) => {
|
|
44108
|
+
const label = CLAIM_TYPES.find((c) => c.type === t.type)?.label ?? "";
|
|
44109
|
+
return [
|
|
44110
|
+
`${t.type} ${label}`,
|
|
44111
|
+
t.supported,
|
|
44112
|
+
t.unverifiable,
|
|
44113
|
+
t.notRecordCheckable,
|
|
44114
|
+
t.contradicted
|
|
44115
|
+
];
|
|
44116
|
+
}),
|
|
44117
|
+
[
|
|
44118
|
+
`All (${a.claims.total})`,
|
|
44119
|
+
a.claims.supported,
|
|
44120
|
+
a.claims.unverifiable,
|
|
44121
|
+
a.claims.notRecordCheckable,
|
|
44122
|
+
a.claims.contradicted
|
|
44123
|
+
]
|
|
44124
|
+
]
|
|
44125
|
+
)
|
|
44126
|
+
);
|
|
44127
|
+
if (a.advisory !== void 0) {
|
|
44128
|
+
lines.push(
|
|
44129
|
+
"",
|
|
44130
|
+
"## Advisory notes",
|
|
44131
|
+
"",
|
|
44132
|
+
`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.`,
|
|
44133
|
+
""
|
|
44134
|
+
);
|
|
44135
|
+
if (a.advisory.notes.length === 0) lines.push("No notes.");
|
|
44136
|
+
else {
|
|
44137
|
+
lines.push(
|
|
44138
|
+
...table2(
|
|
44139
|
+
["Note", "As shown", "Count"],
|
|
44140
|
+
a.advisory.notes.map((n) => [`\`${n.note}\``, noteText(n.note), n.count])
|
|
44141
|
+
)
|
|
44142
|
+
);
|
|
44143
|
+
}
|
|
44144
|
+
}
|
|
44145
|
+
const p = a.pullRequests;
|
|
44146
|
+
const context = { hasToken: record2.selection.reader.authenticated };
|
|
44147
|
+
const remedy = (reason) => remedyForReason(reason, context) ?? "";
|
|
44148
|
+
lines.push(
|
|
44149
|
+
"",
|
|
44150
|
+
"## Pull requests",
|
|
44151
|
+
"",
|
|
44152
|
+
...table2(
|
|
44153
|
+
["Pull requests", "Count", "Remedy"],
|
|
44154
|
+
[
|
|
44155
|
+
["Listed", p.listed, ""],
|
|
44156
|
+
["Selected", p.selected, ""],
|
|
44157
|
+
["With a valid block", p.blockFound, ""],
|
|
44158
|
+
["`block_missing`", p.blockMissing, remedy("block_missing")],
|
|
44159
|
+
["`block_invalid`", p.blockInvalid, remedy("block_invalid")],
|
|
44160
|
+
["`block_ambiguous`", p.blockAmbiguous, remedy("block_ambiguous")],
|
|
44161
|
+
...p.noRecord.map((n) => [`No record: \`${n.reason}\``, n.count, remedy(n.reason)])
|
|
44162
|
+
]
|
|
44163
|
+
),
|
|
44164
|
+
"",
|
|
44165
|
+
`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]}.`
|
|
44166
|
+
);
|
|
44167
|
+
const abandoned = listing.abandoned.length;
|
|
44168
|
+
if (listing.duplicates > 0) {
|
|
44169
|
+
const one = listing.duplicates === 1;
|
|
44170
|
+
const reread = abandoned === 0 ? "" : ` It was read ${abandoned + 1} times, from the first page each time, and moved during every read.`;
|
|
44171
|
+
lines.push(
|
|
44172
|
+
"",
|
|
44173
|
+
`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.${reread}`
|
|
44174
|
+
);
|
|
44175
|
+
} else if (abandoned > 0) {
|
|
44176
|
+
lines.push(
|
|
44177
|
+
"",
|
|
44178
|
+
`The listing moved while it was read: ${plural2(abandoned, "read was", "reads were")} abandoned at a repeated pull request and the listing was read again from the first page. The read used repeated no pull request.`
|
|
44179
|
+
);
|
|
44180
|
+
}
|
|
44181
|
+
if (selected.length < parameters.last) {
|
|
44182
|
+
lines.push(
|
|
44183
|
+
"",
|
|
44184
|
+
`${plural2(selected.length, "pull request was", "pull requests were")} selected, fewer than the ${parameters.last} asked for.`
|
|
44185
|
+
);
|
|
44186
|
+
}
|
|
44187
|
+
lines.push(
|
|
44188
|
+
"",
|
|
44189
|
+
`Verify offline: \`${record2.rerun.offline}\`. Run again: \`${record2.rerun.online}\`.`,
|
|
44190
|
+
""
|
|
44191
|
+
);
|
|
44192
|
+
return lines.join("\n");
|
|
44193
|
+
}
|
|
44194
|
+
|
|
44195
|
+
// src/cli/survey.ts
|
|
44196
|
+
var LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})(?:\[bot\])?$/;
|
|
44197
|
+
function parseLogin(value, flag) {
|
|
44198
|
+
if (!LOGIN.test(value)) throw new UsageError(`${flag} must be a GitHub login`);
|
|
44199
|
+
return value;
|
|
44200
|
+
}
|
|
44201
|
+
function parseSince(value) {
|
|
44202
|
+
const day = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
44203
|
+
const valid = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value) && !Number.isNaN(day.getTime()) && day.toISOString().slice(0, 10) === value;
|
|
44204
|
+
if (!valid) throw new UsageError("--since must be a date, YYYY-MM-DD (UTC)");
|
|
44205
|
+
return value;
|
|
44206
|
+
}
|
|
44207
|
+
async function survey2(args, io) {
|
|
44208
|
+
const { values } = parseArgs5({
|
|
44209
|
+
args,
|
|
44210
|
+
strict: true,
|
|
44211
|
+
allowPositionals: false,
|
|
44212
|
+
options: {
|
|
44213
|
+
repo: { type: "string" },
|
|
44214
|
+
last: { type: "string" },
|
|
44215
|
+
since: { type: "string" },
|
|
44216
|
+
author: { type: "string", multiple: true },
|
|
44217
|
+
"report-comment-author": { type: "string" },
|
|
44218
|
+
advisory: { type: "boolean" },
|
|
44219
|
+
out: { type: "string" },
|
|
44220
|
+
summary: { type: "string" }
|
|
44221
|
+
}
|
|
44222
|
+
});
|
|
44223
|
+
if (values.repo === void 0 || !REPOSITORY.test(values.repo)) {
|
|
44224
|
+
throw new UsageError("--repo <owner/name> is required");
|
|
44225
|
+
}
|
|
44226
|
+
if (values.out === void 0) throw new UsageError("--out <survey.json> is required");
|
|
44227
|
+
const last = values.last === void 0 ? DEFAULT_LAST : parsePositiveInt(values.last, "--last");
|
|
44228
|
+
if (last > MAX_LAST) throw new UsageError(`--last is at most ${MAX_LAST}`);
|
|
44229
|
+
const commentAuthor = values["report-comment-author"];
|
|
44230
|
+
const parameters = {
|
|
44231
|
+
repository: values.repo,
|
|
44232
|
+
last,
|
|
44233
|
+
since: values.since === void 0 ? null : parseSince(values.since),
|
|
44234
|
+
authors: (values.author ?? []).map((a) => parseLogin(a, "--author")),
|
|
44235
|
+
report: commentAuthor === void 0 ? { kind: "pr-body" } : { kind: "pr-comment", author: parseLogin(commentAuthor, "--report-comment-author") },
|
|
44236
|
+
advisory: values.advisory === true
|
|
44237
|
+
};
|
|
44238
|
+
const token = io.env.GITHUB_TOKEN;
|
|
44239
|
+
if (token === void 0 || token === "") {
|
|
44240
|
+
io.err(
|
|
44241
|
+
"dunstan: GITHUB_TOKEN is not set; reading anonymously (GitHub serves no artifact to an anonymous reader)\n"
|
|
44242
|
+
);
|
|
44243
|
+
}
|
|
44244
|
+
const record2 = await survey({
|
|
44245
|
+
parameters,
|
|
44246
|
+
checker: checkerIdentity(io.artifact),
|
|
44247
|
+
out: basename4(values.out),
|
|
44248
|
+
token,
|
|
44249
|
+
...io.fetch === void 0 ? {} : { fetch: io.fetch },
|
|
44250
|
+
...io.now === void 0 ? {} : { now: io.now },
|
|
44251
|
+
warn: (message) => io.err(`dunstan: ${message}
|
|
44252
|
+
`)
|
|
44253
|
+
});
|
|
44254
|
+
writeFileSync5(values.out, serializeSurvey(record2));
|
|
44255
|
+
const summary = renderSurveySummary(record2);
|
|
44256
|
+
if (values.summary === void 0) {
|
|
44257
|
+
io.out(summary);
|
|
44258
|
+
io.err(`dunstan: survey record: ${values.out}
|
|
44259
|
+
`);
|
|
44260
|
+
} else {
|
|
44261
|
+
writeFileSync5(values.summary, summary);
|
|
44262
|
+
io.out(`survey record: ${values.out}
|
|
44263
|
+
summary: ${values.summary}
|
|
44264
|
+
`);
|
|
44265
|
+
}
|
|
44266
|
+
return 0;
|
|
44267
|
+
}
|
|
44268
|
+
|
|
43345
44269
|
// src/cli/template.ts
|
|
43346
|
-
import { parseArgs as
|
|
43347
|
-
|
|
43348
|
-
|
|
44270
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
44271
|
+
|
|
44272
|
+
// src/template/tests-ci.ts
|
|
44273
|
+
var plural3 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
44274
|
+
function unsettled(reason, why, remedy) {
|
|
44275
|
+
const notes = [`tests are not declared: ${why} (${reason}).`];
|
|
44276
|
+
if (remedy !== void 0) notes.push(`remedy: ${remedy}`);
|
|
44277
|
+
return { status: "unsettled", reason, notes };
|
|
44278
|
+
}
|
|
44279
|
+
async function testsFromCi(client, input2) {
|
|
44280
|
+
const { repository, headCommit: head, record: record2 } = input2;
|
|
44281
|
+
if (!client.hasToken) {
|
|
44282
|
+
return unsettled(
|
|
44283
|
+
"no_token",
|
|
44284
|
+
"GITHUB_TOKEN is not set, and GitHub answers an artifact download only with a token, even on a public repository",
|
|
44285
|
+
"set GITHUB_TOKEN to a token that can read the repository, with actions: read, and run dunstan template --tests-ci again."
|
|
44286
|
+
);
|
|
44287
|
+
}
|
|
44288
|
+
if (!input2.pushed) {
|
|
44289
|
+
return unsettled(
|
|
44290
|
+
"not_pushed",
|
|
44291
|
+
`HEAD ${head} is not on any remote-tracking branch, so no CI run can have tested it`,
|
|
44292
|
+
"push it, wait for CI to finish, and run dunstan template --tests-ci again (run git fetch first if it was pushed from elsewhere)."
|
|
44293
|
+
);
|
|
44294
|
+
}
|
|
44295
|
+
const observed = { runsNotCompleted: 0 };
|
|
44296
|
+
const evidence = await readTestRecord(client, repository, record2, head, observed);
|
|
44297
|
+
const named = `the artifact ${record2.artifact} holding ${record2.path} from job ${record2.job}`;
|
|
44298
|
+
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;
|
|
44299
|
+
const again = "run dunstan template --tests-ci again once CI has finished on this commit.";
|
|
44300
|
+
if (evidence.status !== "ok") {
|
|
44301
|
+
const reason = evidence.status === "unreadable" ? `source_unreadable:${evidence.source}` : `evidence_field_unpopulated:${evidence.field}`;
|
|
44302
|
+
if (pending !== void 0) return unsettled(reason, pending, again);
|
|
44303
|
+
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`;
|
|
44304
|
+
return unsettled(
|
|
44305
|
+
reason,
|
|
44306
|
+
why,
|
|
44307
|
+
remedyForReason(reason, { hasToken: true }) ?? "run dunstan template --tests-ci again."
|
|
44308
|
+
);
|
|
44309
|
+
}
|
|
44310
|
+
if (evidence.runs.length === 0) {
|
|
44311
|
+
if (pending !== void 0) {
|
|
44312
|
+
return unsettled(
|
|
44313
|
+
"record_not_found",
|
|
44314
|
+
`${pending}, and no completed run uploaded ${named}`,
|
|
44315
|
+
again
|
|
44316
|
+
);
|
|
44317
|
+
}
|
|
44318
|
+
return unsettled(
|
|
44319
|
+
"record_not_found",
|
|
44320
|
+
`no run of ${record2.workflow} at ${head} uploaded ${named}`,
|
|
44321
|
+
remedyForReason("record_not_found", { hasToken: true })
|
|
44322
|
+
);
|
|
44323
|
+
}
|
|
44324
|
+
const executed = new Set(evidence.runs.map((r) => r.executed));
|
|
44325
|
+
const failed = new Set(evidence.runs.map((r) => r.failed));
|
|
44326
|
+
if (executed.size > 1 || failed.size > 1) {
|
|
44327
|
+
const counts = evidence.runs.map((r) => `run ${r.runId}: ${r.executed} executed, ${r.failed} failed`).join("; ");
|
|
44328
|
+
return unsettled(
|
|
44329
|
+
"record_ambiguous",
|
|
44330
|
+
`the runs of ${record2.workflow} at ${head} that uploaded ${record2.artifact} disagree (${counts}), and the gate never picks one`,
|
|
44331
|
+
"make the runs agree (re-run the one that differs), or name a job only one of them runs."
|
|
44332
|
+
);
|
|
44333
|
+
}
|
|
44334
|
+
const run = evidence.runs[0];
|
|
44335
|
+
const runs = evidence.runs.map((r) => r.runId).join(", ");
|
|
44336
|
+
const notes = [
|
|
44337
|
+
`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}).`
|
|
44338
|
+
];
|
|
44339
|
+
if (pending !== void 0) {
|
|
44340
|
+
notes.push(
|
|
44341
|
+
`${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.`
|
|
44342
|
+
);
|
|
44343
|
+
}
|
|
44344
|
+
return {
|
|
44345
|
+
status: "settled",
|
|
44346
|
+
run: { command: input2.command, count: run.executed, failures: run.failed, record: record2 },
|
|
44347
|
+
notes
|
|
44348
|
+
};
|
|
44349
|
+
}
|
|
44350
|
+
|
|
44351
|
+
// src/cli/template.ts
|
|
44352
|
+
var RECORD_FLAGS = [
|
|
43349
44353
|
"tests-workflow",
|
|
43350
44354
|
"tests-job",
|
|
43351
44355
|
"tests-artifact",
|
|
43352
44356
|
"tests-artifact-path"
|
|
43353
44357
|
];
|
|
44358
|
+
var DEFAULT_ARTIFACT = "dunstan-junit";
|
|
44359
|
+
var DEFAULT_ARTIFACT_PATH = "junit.xml";
|
|
43354
44360
|
async function template(args, io) {
|
|
43355
|
-
const { values } =
|
|
44361
|
+
const { values } = parseArgs6({
|
|
43356
44362
|
args,
|
|
43357
44363
|
strict: true,
|
|
43358
44364
|
allowPositionals: false,
|
|
43359
44365
|
options: {
|
|
43360
44366
|
base: { type: "string" },
|
|
44367
|
+
"tests-ci": { type: "boolean" },
|
|
44368
|
+
"tests-local": { type: "string" },
|
|
43361
44369
|
"tests-junit": { type: "string" },
|
|
43362
|
-
|
|
44370
|
+
"tests-command": { type: "string" },
|
|
44371
|
+
repo: { type: "string" },
|
|
44372
|
+
...Object.fromEntries(RECORD_FLAGS.map((f) => [f, { type: "string" }]))
|
|
43363
44373
|
}
|
|
43364
44374
|
});
|
|
43365
44375
|
const v = values;
|
|
44376
|
+
const flag = (name) => v[name];
|
|
44377
|
+
const ci = v["tests-ci"] === true;
|
|
44378
|
+
const modes = [ci, flag("tests-local") !== void 0, flag("tests-junit") !== void 0];
|
|
44379
|
+
if (modes.filter(Boolean).length > 1) {
|
|
44380
|
+
throw new UsageError("give one of --tests-ci, --tests-local or --tests-junit");
|
|
44381
|
+
}
|
|
44382
|
+
if (flag("repo") !== void 0 && !ci) throw new UsageError("--repo goes with --tests-ci");
|
|
44383
|
+
const given = ["tests-command", ...RECORD_FLAGS].filter((f) => flag(f) !== void 0);
|
|
44384
|
+
const cwd = io.cwd ?? process.cwd();
|
|
43366
44385
|
let tests;
|
|
43367
|
-
|
|
43368
|
-
|
|
43369
|
-
|
|
43370
|
-
|
|
44386
|
+
if (ci) {
|
|
44387
|
+
const missing = ["tests-command", "tests-workflow", "tests-job"].filter(
|
|
44388
|
+
(f) => flag(f) === void 0
|
|
44389
|
+
);
|
|
44390
|
+
if (missing.length > 0) {
|
|
44391
|
+
throw new UsageError(`--tests-ci needs ${missing.map((f) => `--${f}`).join(", ")}`);
|
|
44392
|
+
}
|
|
44393
|
+
const repo = flag("repo");
|
|
44394
|
+
if (repo !== void 0 && !REPOSITORY.test(repo)) {
|
|
44395
|
+
throw new UsageError("--repo must be <owner/name>");
|
|
44396
|
+
}
|
|
44397
|
+
const head = headCommit(cwd);
|
|
44398
|
+
const repository = repo ?? originRepository(cwd);
|
|
44399
|
+
const client = new GitHubClient({
|
|
44400
|
+
token: io.env.GITHUB_TOKEN,
|
|
44401
|
+
...io.fetch === void 0 ? {} : { fetch: io.fetch },
|
|
44402
|
+
warn: (message) => io.err(`dunstan: ${message}
|
|
44403
|
+
`)
|
|
44404
|
+
});
|
|
44405
|
+
const read = await testsFromCi(client, {
|
|
44406
|
+
repository,
|
|
44407
|
+
headCommit: head,
|
|
44408
|
+
command: flag("tests-command"),
|
|
44409
|
+
pushed: isPushed(cwd, head),
|
|
44410
|
+
record: {
|
|
44411
|
+
kind: "junit",
|
|
44412
|
+
workflow: flag("tests-workflow"),
|
|
44413
|
+
job: flag("tests-job"),
|
|
44414
|
+
artifact: flag("tests-artifact") ?? DEFAULT_ARTIFACT,
|
|
44415
|
+
path: flag("tests-artifact-path") ?? DEFAULT_ARTIFACT_PATH
|
|
44416
|
+
}
|
|
44417
|
+
});
|
|
44418
|
+
tests = { kind: "ci", read };
|
|
44419
|
+
} else if (flag("tests-local") !== void 0) {
|
|
44420
|
+
if (flag("tests-command") === void 0) {
|
|
44421
|
+
throw new UsageError("--tests-local needs --tests-command <cmd>");
|
|
43371
44422
|
}
|
|
43372
|
-
|
|
43373
|
-
|
|
44423
|
+
const extra = RECORD_FLAGS.find((f) => flag(f) !== void 0);
|
|
44424
|
+
if (extra !== void 0) throw new UsageError(`--${extra} does not go with --tests-local`);
|
|
44425
|
+
tests = {
|
|
44426
|
+
kind: "local",
|
|
44427
|
+
junit: flag("tests-local"),
|
|
44428
|
+
command: flag("tests-command")
|
|
44429
|
+
};
|
|
44430
|
+
} else if (flag("tests-junit") !== void 0) {
|
|
44431
|
+
const missing = ["tests-command", ...RECORD_FLAGS].filter(
|
|
44432
|
+
(f) => flag(f) === void 0
|
|
44433
|
+
);
|
|
43374
44434
|
if (missing.length > 0) {
|
|
43375
44435
|
throw new UsageError(
|
|
43376
44436
|
`--tests-junit needs the CI record its counts are checked against: ${missing.map((f) => `--${f}`).join(", ")}`
|
|
43377
44437
|
);
|
|
43378
44438
|
}
|
|
43379
44439
|
tests = {
|
|
43380
|
-
|
|
43381
|
-
|
|
44440
|
+
kind: "junit",
|
|
44441
|
+
junit: flag("tests-junit"),
|
|
44442
|
+
command: flag("tests-command"),
|
|
43382
44443
|
record: {
|
|
43383
|
-
workflow:
|
|
43384
|
-
job:
|
|
43385
|
-
artifact:
|
|
43386
|
-
path:
|
|
44444
|
+
workflow: flag("tests-workflow"),
|
|
44445
|
+
job: flag("tests-job"),
|
|
44446
|
+
artifact: flag("tests-artifact"),
|
|
44447
|
+
path: flag("tests-artifact-path")
|
|
43387
44448
|
}
|
|
43388
44449
|
};
|
|
44450
|
+
} else if (given.length > 0) {
|
|
44451
|
+
throw new UsageError(`--${given[0]} goes with --tests-ci, --tests-local or --tests-junit`);
|
|
43389
44452
|
}
|
|
43390
|
-
const t = templateBlock({ cwd
|
|
44453
|
+
const t = templateBlock({ cwd, base: flag("base"), tests });
|
|
43391
44454
|
io.out(renderBlock(t.block));
|
|
43392
44455
|
for (const note of t.notes) io.err(`dunstan: ${note}
|
|
43393
44456
|
`);
|
|
43394
|
-
return 0;
|
|
44457
|
+
return tests?.kind === "ci" && tests.read.status === "unsettled" ? EXIT.unverifiable : 0;
|
|
43395
44458
|
}
|
|
43396
44459
|
|
|
43397
44460
|
// src/cli/verify.ts
|
|
43398
|
-
import { readFileSync as
|
|
43399
|
-
import { parseArgs as
|
|
44461
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
44462
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
43400
44463
|
|
|
43401
44464
|
// src/record/verify.ts
|
|
43402
44465
|
function same(a, b) {
|
|
@@ -43451,10 +44514,10 @@ function verifyRecord(value) {
|
|
|
43451
44514
|
const claims = claimsFor(p.block, evidence, p.subject.repository);
|
|
43452
44515
|
if (!same(p.claims, claims)) {
|
|
43453
44516
|
const index = claims.findIndex((c, i) => !same(c, p.claims[i]));
|
|
43454
|
-
const
|
|
44517
|
+
const at2 = index === -1 ? claims.length : index;
|
|
43455
44518
|
problems.push({
|
|
43456
|
-
member: `/predicate/claims/${
|
|
43457
|
-
message: `recorded ${canonicalize(p.claims[
|
|
44519
|
+
member: `/predicate/claims/${at2}`,
|
|
44520
|
+
message: `recorded ${canonicalize(p.claims[at2] ?? null)}, recomputed ${canonicalize(claims[at2] ?? null)}`
|
|
43458
44521
|
});
|
|
43459
44522
|
}
|
|
43460
44523
|
differ("/predicate/verdict", p.verdict, overallVerdict(p.block.status, claims));
|
|
@@ -43511,9 +44574,225 @@ function verifyRecord(value) {
|
|
|
43511
44574
|
return { ok: problems.length === 0, problems, record: record2 };
|
|
43512
44575
|
}
|
|
43513
44576
|
|
|
44577
|
+
// src/survey/verify.ts
|
|
44578
|
+
var text = (value) => canonicalize(value ?? null);
|
|
44579
|
+
function differences(a, b, pointer) {
|
|
44580
|
+
const isObject2 = (v) => v !== null && typeof v === "object";
|
|
44581
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
44582
|
+
const out = [];
|
|
44583
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
44584
|
+
out.push(...differences(a[i], b[i], `${pointer}/${i}`));
|
|
44585
|
+
}
|
|
44586
|
+
return out;
|
|
44587
|
+
}
|
|
44588
|
+
if (isObject2(a) && isObject2(b) && !Array.isArray(a) && !Array.isArray(b)) {
|
|
44589
|
+
const ao = a;
|
|
44590
|
+
const bo = b;
|
|
44591
|
+
const keys = [.../* @__PURE__ */ new Set([...Object.keys(ao), ...Object.keys(bo)])].sort();
|
|
44592
|
+
return keys.flatMap((k) => differences(ao[k], bo[k], `${pointer}/${escapePointerToken(k)}`));
|
|
44593
|
+
}
|
|
44594
|
+
return text(a) === text(b) ? [] : [pointer];
|
|
44595
|
+
}
|
|
44596
|
+
function at(value, pointer, base) {
|
|
44597
|
+
let v = value;
|
|
44598
|
+
for (const token of pointer.slice(base.length).split("/").slice(1)) {
|
|
44599
|
+
const key = token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
44600
|
+
v = v !== null && typeof v === "object" ? v[key] : void 0;
|
|
44601
|
+
}
|
|
44602
|
+
return v;
|
|
44603
|
+
}
|
|
44604
|
+
function abandonedProblems(listing, repeated) {
|
|
44605
|
+
const problems = [];
|
|
44606
|
+
const base = "/selection/listing/abandoned";
|
|
44607
|
+
const { abandoned } = listing;
|
|
44608
|
+
abandoned.forEach((read, r) => {
|
|
44609
|
+
if (read.pages.length > listing.pageBound) {
|
|
44610
|
+
problems.push({
|
|
44611
|
+
member: `${base}/${r}/pages`,
|
|
44612
|
+
message: `${read.pages.length} pages read, past the bound of ${listing.pageBound}`
|
|
44613
|
+
});
|
|
44614
|
+
}
|
|
44615
|
+
read.pages.forEach((page, i) => {
|
|
44616
|
+
const at2 = `${base}/${r}/pages/${i}`;
|
|
44617
|
+
if (page.page !== i + 1) {
|
|
44618
|
+
problems.push({
|
|
44619
|
+
member: `${at2}/page`,
|
|
44620
|
+
message: `recorded ${page.page}; a read runs from page 1 without a gap, so this is page ${i + 1}`
|
|
44621
|
+
});
|
|
44622
|
+
}
|
|
44623
|
+
if (i < read.pages.length - 1) {
|
|
44624
|
+
if (page.duplicates !== 0) {
|
|
44625
|
+
problems.push({
|
|
44626
|
+
member: `${at2}/duplicates`,
|
|
44627
|
+
message: `recorded ${page.duplicates}; a read is abandoned at its first page that repeats an earlier item, so no page before its last repeats one`
|
|
44628
|
+
});
|
|
44629
|
+
}
|
|
44630
|
+
if (page.error !== void 0) {
|
|
44631
|
+
problems.push({
|
|
44632
|
+
member: `${at2}/error`,
|
|
44633
|
+
message: `recorded ${text(page.error)}; a page that could not be read ends its read, so only a last page has an error`
|
|
44634
|
+
});
|
|
44635
|
+
}
|
|
44636
|
+
} else if (page.duplicates === 0 || page.duplicates > page.items) {
|
|
44637
|
+
problems.push({
|
|
44638
|
+
member: `${at2}/duplicates`,
|
|
44639
|
+
message: page.duplicates === 0 ? "recorded 0; an abandoned read ends at a page that repeats an earlier item" : `recorded ${page.duplicates}, more than the page's ${page.items} items`
|
|
44640
|
+
});
|
|
44641
|
+
}
|
|
44642
|
+
});
|
|
44643
|
+
});
|
|
44644
|
+
if (repeated > 0 && abandoned.length !== LISTING_READS - 1) {
|
|
44645
|
+
problems.push({
|
|
44646
|
+
member: base,
|
|
44647
|
+
message: `${abandoned.length} reads abandoned, but the read used repeated ${repeated} items: a read that repeats is abandoned unless it is the last of ${LISTING_READS} reads, so ${LISTING_READS - 1} reads were abandoned before it`
|
|
44648
|
+
});
|
|
44649
|
+
}
|
|
44650
|
+
const reads = [...abandoned.map((read) => read.pages), listing.pages];
|
|
44651
|
+
reads.slice(1).forEach((later2, r) => {
|
|
44652
|
+
const first = later2[0];
|
|
44653
|
+
if (first === void 0) return;
|
|
44654
|
+
const next = Date.parse(first.readAt);
|
|
44655
|
+
(reads[r] ?? []).forEach((page, i) => {
|
|
44656
|
+
if (Date.parse(page.readAt) > next) {
|
|
44657
|
+
problems.push({
|
|
44658
|
+
member: `${base}/${r}/pages/${i}/readAt`,
|
|
44659
|
+
message: `recorded ${text(page.readAt)}, after the next read's first page (${text(first.readAt)})`
|
|
44660
|
+
});
|
|
44661
|
+
}
|
|
44662
|
+
});
|
|
44663
|
+
});
|
|
44664
|
+
return problems;
|
|
44665
|
+
}
|
|
44666
|
+
function verifySurveyRecord(value) {
|
|
44667
|
+
const schemaErrors = validateSurveyRecord(value);
|
|
44668
|
+
if (schemaErrors.length > 0) {
|
|
44669
|
+
return {
|
|
44670
|
+
ok: false,
|
|
44671
|
+
problems: schemaErrors.map((e) => ({
|
|
44672
|
+
member: e.pointer,
|
|
44673
|
+
message: `schema_violation (${e.keyword}): ${e.message}`
|
|
44674
|
+
}))
|
|
44675
|
+
};
|
|
44676
|
+
}
|
|
44677
|
+
const record2 = value;
|
|
44678
|
+
const problems = [];
|
|
44679
|
+
const differ = (member, recorded, recomputed) => {
|
|
44680
|
+
for (const pointer of differences(recorded, recomputed, member)) {
|
|
44681
|
+
problems.push({
|
|
44682
|
+
member: pointer,
|
|
44683
|
+
message: `recorded ${text(at(recorded, pointer, member))}, recomputed ${text(at(recomputed, pointer, member))}`
|
|
44684
|
+
});
|
|
44685
|
+
}
|
|
44686
|
+
};
|
|
44687
|
+
const { checker, selection, entries } = record2;
|
|
44688
|
+
if (checker.name !== CHECKER_NAME || checker.version !== CHECKER_VERSION) {
|
|
44689
|
+
problems.push({
|
|
44690
|
+
member: "/checker",
|
|
44691
|
+
message: `survey was written by ${checker.name} ${checker.version}; this is ${CHECKER_NAME} ${CHECKER_VERSION}`
|
|
44692
|
+
});
|
|
44693
|
+
return { ok: false, problems, record: record2 };
|
|
44694
|
+
}
|
|
44695
|
+
const { parameters, listing } = selection;
|
|
44696
|
+
const firstAt = /* @__PURE__ */ new Map();
|
|
44697
|
+
listing.pullRequests.forEach((pr, i) => {
|
|
44698
|
+
const first = firstAt.get(pr.number);
|
|
44699
|
+
if (first === void 0) firstAt.set(pr.number, i);
|
|
44700
|
+
else {
|
|
44701
|
+
problems.push({
|
|
44702
|
+
member: `/selection/listing/pullRequests/${i}`,
|
|
44703
|
+
message: `pull request ${pr.number} is listed again (first at /selection/listing/pullRequests/${first}); the listing holds each number once`
|
|
44704
|
+
});
|
|
44705
|
+
}
|
|
44706
|
+
});
|
|
44707
|
+
const held = listing.pages.reduce((sum, page) => sum + page.items, 0);
|
|
44708
|
+
const repeated = listing.pages.reduce((sum, page) => sum + page.duplicates, 0);
|
|
44709
|
+
listing.pages.forEach((page, i) => {
|
|
44710
|
+
if (page.duplicates > page.items) {
|
|
44711
|
+
problems.push({
|
|
44712
|
+
member: `/selection/listing/pages/${i}/duplicates`,
|
|
44713
|
+
message: `recorded ${page.duplicates}, more than the page's ${page.items} items`
|
|
44714
|
+
});
|
|
44715
|
+
}
|
|
44716
|
+
});
|
|
44717
|
+
differ("/selection/listing/duplicates", listing.duplicates, repeated);
|
|
44718
|
+
differ(
|
|
44719
|
+
"/selection/listing/pullRequests/length",
|
|
44720
|
+
listing.pullRequests.length,
|
|
44721
|
+
held - listing.duplicates
|
|
44722
|
+
);
|
|
44723
|
+
let read = 0;
|
|
44724
|
+
listing.pages.forEach((page, i) => {
|
|
44725
|
+
read += page.items - page.duplicates;
|
|
44726
|
+
if (i === listing.pages.length - 1) return;
|
|
44727
|
+
const stop2 = listingStop(
|
|
44728
|
+
parameters,
|
|
44729
|
+
listing.pages.slice(0, i + 1),
|
|
44730
|
+
listing.pullRequests.slice(0, read)
|
|
44731
|
+
);
|
|
44732
|
+
if (stop2 !== null) {
|
|
44733
|
+
problems.push({
|
|
44734
|
+
member: `/selection/listing/pages/${i + 1}`,
|
|
44735
|
+
message: `the listing ended at page ${page.page} (${stop2}); no later page is read`
|
|
44736
|
+
});
|
|
44737
|
+
}
|
|
44738
|
+
});
|
|
44739
|
+
const stop = listingStop(parameters, listing.pages, listing.pullRequests);
|
|
44740
|
+
const end = stop ?? (listing.pages.length === listing.pageBound ? "page_bound" : null);
|
|
44741
|
+
if (end === null) {
|
|
44742
|
+
problems.push({
|
|
44743
|
+
member: "/selection/listing/end",
|
|
44744
|
+
message: `recorded ${text(listing.end)}, recomputed none: the listing was not read to an end`
|
|
44745
|
+
});
|
|
44746
|
+
} else {
|
|
44747
|
+
differ("/selection/listing/end", listing.end, end);
|
|
44748
|
+
}
|
|
44749
|
+
if (listing.pages.length > listing.pageBound) {
|
|
44750
|
+
problems.push({
|
|
44751
|
+
member: "/selection/listing/pages",
|
|
44752
|
+
message: `${listing.pages.length} pages read, past the bound of ${listing.pageBound}`
|
|
44753
|
+
});
|
|
44754
|
+
}
|
|
44755
|
+
problems.push(...abandonedProblems(listing, repeated));
|
|
44756
|
+
const selected = selectPullRequests(parameters, listing.pullRequests);
|
|
44757
|
+
differ("/selection/selected", selection.selected, selected);
|
|
44758
|
+
differ(
|
|
44759
|
+
"/entries/pullRequests",
|
|
44760
|
+
entries.map((e) => e.pullRequest),
|
|
44761
|
+
selected
|
|
44762
|
+
);
|
|
44763
|
+
entries.forEach((entry, i) => {
|
|
44764
|
+
if (entry.record === null) return;
|
|
44765
|
+
const base = `/entries/${i}/record`;
|
|
44766
|
+
const result = verifyRecord(entry.record);
|
|
44767
|
+
for (const p of result.problems)
|
|
44768
|
+
problems.push({ member: `${base}${p.member}`, message: p.message });
|
|
44769
|
+
const subject = entry.record.predicate.subject;
|
|
44770
|
+
differ(`${base}/predicate/subject/pullRequest`, subject.pullRequest, entry.pullRequest);
|
|
44771
|
+
differ(
|
|
44772
|
+
`${base}/predicate/subject/repository`,
|
|
44773
|
+
subject.repository.toLowerCase(),
|
|
44774
|
+
parameters.repository.toLowerCase()
|
|
44775
|
+
);
|
|
44776
|
+
differ(`${base}/predicate/checker`, entry.record.predicate.checker, checker);
|
|
44777
|
+
});
|
|
44778
|
+
try {
|
|
44779
|
+
differ(
|
|
44780
|
+
"/aggregate",
|
|
44781
|
+
record2.aggregate,
|
|
44782
|
+
aggregate(entries, listing.pullRequests.length, parameters.advisory, selection.reader)
|
|
44783
|
+
);
|
|
44784
|
+
} catch (e) {
|
|
44785
|
+
problems.push({ member: "/aggregate", message: `not recomputed: ${e.message}` });
|
|
44786
|
+
}
|
|
44787
|
+
const out = record2.rerun.offline.startsWith("dunstan verify ") ? record2.rerun.offline.slice("dunstan verify ".length) : "";
|
|
44788
|
+
differ("/rerun", record2.rerun, surveyRerun(parameters, out));
|
|
44789
|
+
differ("/digest/sha256", record2.digest.sha256, surveyDigest(record2));
|
|
44790
|
+
return { ok: problems.length === 0, problems, record: record2 };
|
|
44791
|
+
}
|
|
44792
|
+
|
|
43514
44793
|
// src/cli/verify.ts
|
|
43515
44794
|
async function verify(args, io) {
|
|
43516
|
-
const { values, positionals } =
|
|
44795
|
+
const { values, positionals } = parseArgs7({
|
|
43517
44796
|
args,
|
|
43518
44797
|
strict: true,
|
|
43519
44798
|
allowPositionals: true,
|
|
@@ -43526,7 +44805,7 @@ async function verify(args, io) {
|
|
|
43526
44805
|
throw new UsageError("--sig and --allowed-signers go together");
|
|
43527
44806
|
}
|
|
43528
44807
|
const path = positionals[0];
|
|
43529
|
-
const bytes =
|
|
44808
|
+
const bytes = readFileSync7(path);
|
|
43530
44809
|
let value;
|
|
43531
44810
|
try {
|
|
43532
44811
|
value = parseStrictJson(new TextDecoder("utf-8").decode(bytes));
|
|
@@ -43536,6 +44815,25 @@ async function verify(args, io) {
|
|
|
43536
44815
|
`);
|
|
43537
44816
|
return 1;
|
|
43538
44817
|
}
|
|
44818
|
+
if (isSurveyRecord(value)) {
|
|
44819
|
+
if (sig !== void 0) throw new UsageError("a survey record is not signed");
|
|
44820
|
+
const survey3 = verifySurveyRecord(value);
|
|
44821
|
+
if (!survey3.ok) {
|
|
44822
|
+
io.out(`NOT verified: ${path}
|
|
44823
|
+
`);
|
|
44824
|
+
for (const problem of survey3.problems) {
|
|
44825
|
+
io.out(` ${problem.member || "/"}: ${problem.message}
|
|
44826
|
+
`);
|
|
44827
|
+
}
|
|
44828
|
+
return 1;
|
|
44829
|
+
}
|
|
44830
|
+
const s = survey3.record;
|
|
44831
|
+
io.out(
|
|
44832
|
+
`verified: ${path}: survey of ${s?.entries.length} pull requests; every embedded record, the selection, the aggregate and the digest recompute
|
|
44833
|
+
`
|
|
44834
|
+
);
|
|
44835
|
+
return 0;
|
|
44836
|
+
}
|
|
43539
44837
|
const result = verifyRecord(value);
|
|
43540
44838
|
const problems = [...result.problems];
|
|
43541
44839
|
const assurance = result.record?.predicate.assurance;
|
|
@@ -43589,9 +44887,16 @@ usage:
|
|
|
43589
44887
|
dunstan verify <record.json> [--sig <file> --allowed-signers <file>]
|
|
43590
44888
|
dunstan rerun <record.json> [--out <record.json>] [--exclude-check-run <id>]...
|
|
43591
44889
|
dunstan template [--base <ref>]
|
|
43592
|
-
[--tests-
|
|
44890
|
+
[--tests-ci --tests-command <cmd> --tests-workflow <path> --tests-job <name>
|
|
44891
|
+
[--tests-artifact <name>] [--tests-artifact-path <path>] [--repo <owner/name>]
|
|
44892
|
+
| --tests-local <file> --tests-command <cmd>
|
|
44893
|
+
| --tests-junit <file> --tests-command <cmd> --tests-workflow <path>
|
|
43593
44894
|
--tests-job <name> --tests-artifact <name> --tests-artifact-path <path>]
|
|
43594
44895
|
dunstan suggest [--report-file <path|->]
|
|
44896
|
+
dunstan survey --repo <owner/name> --out <survey.json> [--summary <survey.md>]
|
|
44897
|
+
[--last <n>] [--since <YYYY-MM-DD>] [--author <login>]...
|
|
44898
|
+
[--report-comment-author <login>] [--advisory]
|
|
44899
|
+
dunstan junit-merge <file>... --out <file>
|
|
43595
44900
|
dunstan mcp
|
|
43596
44901
|
|
|
43597
44902
|
check reads the pull request, the named report and the evidence its block needs, writes the
|
|
@@ -43601,15 +44906,30 @@ check reads the pull request, the named report and the evidence its block ne
|
|
|
43601
44906
|
verify offline: recomputes claims, verdict and digests from the record and compares them.
|
|
43602
44907
|
rerun online: re-reads the sources for the recorded subject and block; writes a new record.
|
|
43603
44908
|
template local git: prints a dunstan-handback block for HEAD, its files against the merge base
|
|
43604
|
-
with --base (default: the remote's default branch). Tests
|
|
43605
|
-
the CI record
|
|
44909
|
+
with --base (default: the remote's default branch). Tests: --tests-ci reads the counts
|
|
44910
|
+
at HEAD from the CI record check reads (artifact dunstan-junit, file junit.xml unless
|
|
44911
|
+
given; the repository origin names on github.com unless --repo), with GITHUB_TOKEN;
|
|
44912
|
+
--tests-local declares a local JUnit file's counts as a local run; --tests-junit
|
|
44913
|
+
declares them against a CI record. Only --tests-ci reads the network. References,
|
|
44914
|
+
checks and mergedAt never (docs/agents.md, docs/ci-test-records.md).
|
|
43606
44915
|
suggest offline: lists sentences in the report's prose (a file, or stdin) that look like claims
|
|
43607
44916
|
its block does not declare, to declare or reword. Never a verdict (docs/agents.md).
|
|
44917
|
+
survey read-only, DRAFT (docs/survey.md): checks the --last (default 30, at most 200) most
|
|
44918
|
+
recently created closed pull requests as check does, the report in each body or the
|
|
44919
|
+
named author's latest comment; writes the survey record and a Markdown summary of the
|
|
44920
|
+
claims the record could not settle, why, and the contradicted share of the claims it
|
|
44921
|
+
settled. verify recomputes a survey record. GITHUB_TOKEN is optional; the record says
|
|
44922
|
+
whether it was set.
|
|
44923
|
+
junit-merge
|
|
44924
|
+
offline: one JUnit file from several (Maven Surefire, Gradle), inputs in code-unit
|
|
44925
|
+
order of path, each copied whole; refused unless its counts are the inputs' sum.
|
|
43608
44926
|
mcp serves dunstan_check_handback and dunstan_suggest_declarations over stdio
|
|
43609
44927
|
(docs/mcp.md): check's checker and suggest, called by an agent before it says done.
|
|
43610
44928
|
|
|
43611
|
-
exit codes: 0 pass (verify: verified; template: printed; suggest: always
|
|
43612
|
-
|
|
44929
|
+
exit codes: 0 pass (verify: verified; template: printed; suggest: always; survey: written;
|
|
44930
|
+
junit-merge: written), 1 fail (verify: not verified), 2 unverifiable (template:
|
|
44931
|
+
printed without the tests --tests-ci asked for), 3 usage or internal error, git
|
|
44932
|
+
cannot answer (template), or refused (junit-merge)
|
|
43613
44933
|
`;
|
|
43614
44934
|
async function main(argv, io) {
|
|
43615
44935
|
const [command, ...args] = argv;
|
|
@@ -43625,6 +44945,10 @@ async function main(argv, io) {
|
|
|
43625
44945
|
return await template(args, io);
|
|
43626
44946
|
case "suggest":
|
|
43627
44947
|
return await suggest(args, io);
|
|
44948
|
+
case "survey":
|
|
44949
|
+
return await survey2(args, io);
|
|
44950
|
+
case "junit-merge":
|
|
44951
|
+
return await junitMerge(args, io);
|
|
43628
44952
|
case "mcp":
|
|
43629
44953
|
return await mcp(args, io);
|
|
43630
44954
|
case "--version":
|
|
@@ -43657,11 +44981,11 @@ ${USAGE}`);
|
|
|
43657
44981
|
|
|
43658
44982
|
// src/cli/bin.ts
|
|
43659
44983
|
process.exitCode = await main(process.argv.slice(2), {
|
|
43660
|
-
out: (
|
|
43661
|
-
err: (
|
|
44984
|
+
out: (text2) => process.stdout.write(text2),
|
|
44985
|
+
err: (text2) => process.stderr.write(text2),
|
|
43662
44986
|
env: process.env,
|
|
43663
44987
|
artifact: import.meta.url,
|
|
43664
|
-
readStdin: () =>
|
|
44988
|
+
readStdin: () => readFileSync8(0),
|
|
43665
44989
|
stdin: process.stdin,
|
|
43666
44990
|
stdout: process.stdout
|
|
43667
44991
|
});
|