@outerlayer/cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +10 -0
- package/dist/{agent-setup-USZ2SAXC.js → agent-setup-TXLDY5RV.js} +7 -6
- package/dist/{build-PAEKVYVK.js → build-JF5UOWKJ.js} +2 -1
- package/dist/build-info.json +1 -1
- package/dist/{check-CCVPBN7A.js → check-ASZL5BEO.js} +29 -23
- package/dist/{chunk-ZILGTTY6.js → chunk-2VO25HHO.js} +3 -3
- package/dist/{chunk-OIRNH6UO.js → chunk-5FWNEQNZ.js} +3 -3
- package/dist/{chunk-MXB5VB4Y.js → chunk-6IYLDODX.js} +6 -6
- package/dist/{chunk-4NLKY4WR.js → chunk-6QWV3YDJ.js} +2 -2
- package/dist/{chunk-UMFWVGJO.js → chunk-6ZZHWY2Z.js} +4 -3
- package/dist/{chunk-NQXFAGHU.js → chunk-7FTNIAHS.js} +1 -1
- package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
- package/dist/{chunk-43J3M6KB.js → chunk-AII7CKTH.js} +45 -31
- package/dist/{chunk-VMJ3MMBX.js → chunk-AJKEHABI.js} +312 -18
- package/dist/{chunk-RBOMWNOG.js → chunk-AQ54MKSQ.js} +2 -2
- package/dist/{chunk-Z62HNKEJ.js → chunk-CEBIA5FD.js} +31 -5
- package/dist/{chunk-ANK2B36Y.js → chunk-D6FXM5OW.js} +6182 -28607
- package/dist/{chunk-BNMCXPG7.js → chunk-DGAIVMMN.js} +12 -3
- package/dist/{chunk-3DD6AOWC.js → chunk-DTXOJDEH.js} +26 -2
- package/dist/{chunk-RZSF5B46.js → chunk-E4PIFIC3.js} +100 -15
- package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
- package/dist/chunk-HKMPVPO7.js +55 -0
- package/dist/{chunk-FA2BOX4O.js → chunk-JPR2WHHF.js} +1 -1
- package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
- package/dist/{chunk-MIGEDH5P.js → chunk-MQPRB5K7.js} +9 -9
- package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
- package/dist/chunk-PUN7LMNS.js +39 -0
- package/dist/{chunk-JWOQNNN2.js → chunk-Q5G2ODUZ.js} +1 -1
- package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
- package/dist/{chunk-PMAMOSTP.js → chunk-SSU747O6.js} +85 -25
- package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
- package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
- package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
- package/dist/{chunk-IASWYRVG.js → chunk-X2FSFU4C.js} +4 -1
- package/dist/chunk-YNJEOGDL.js +22434 -0
- package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
- package/dist/{chunk-3DEVYTNQ.js → chunk-ZYHKJKSU.js} +1 -1
- package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
- package/dist/{cli-7WCNPBYC.js → cli-LEVCGAQV.js} +73 -71
- package/dist/{command-ARFK3OYF.js → command-FLSYCGIB.js} +47 -22
- package/dist/config-I3TVFYLH.js +10 -0
- package/dist/{connect-cmd-PCESZLTR.js → connect-cmd-MJMUONDY.js} +4 -4
- package/dist/{context-adopt-G5JAELE7.js → context-adopt-CE4KDXS3.js} +5 -4
- package/dist/{context-materialize-3HLPZAF6.js → context-materialize-QL33HDJN.js} +5 -4
- package/dist/{dist-RDLP4GGJ.js → dist-PXBSA7QX.js} +2 -1
- package/dist/{doctor-VOMYOHQU.js → doctor-FVDT3KJZ.js} +23 -16
- package/dist/{emit-artifact-cmd-TBF46VXZ.js → emit-artifact-cmd-7RPMRSVP.js} +11 -4
- package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
- package/dist/{emit-criteria-cmd-3427XKFW.js → emit-criteria-cmd-MKI7WETO.js} +10 -1
- package/dist/{emit-finding-cmd-GSI75K36.js → emit-finding-cmd-RMLGZASB.js} +12 -1
- package/dist/{emit-result-cmd-3PRQKB55.js → emit-result-cmd-MTXAZ2GV.js} +13 -6
- package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
- package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
- package/dist/{hook-fast-NJBBZQ5Q.js → hook-fast-XBOZ3N6L.js} +1 -1
- package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
- package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
- package/dist/index.js +3 -3
- package/dist/{init-WVR4BZQ4.js → init-BACXESVV.js} +21 -14
- package/dist/{init-OHYFIKZD.js → init-VLW7H6RW.js} +22 -15
- package/dist/{init-cmd-HMFNIFPI.js → init-cmd-VJFB5L7X.js} +10 -9
- package/dist/{install-DSUZE2TJ.js → install-NMAPLLBQ.js} +4 -3
- package/dist/{install-cmd-453C7IZT.js → install-cmd-B5SEU3G7.js} +2 -2
- package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
- package/dist/{loop-UAJB34KA.js → loop-D3243JKT.js} +29 -23
- package/dist/{machine-2JOWGRSZ.js → machine-EB3GEVI4.js} +20 -13
- package/dist/{session-sender-ED26V45B.js → session-sender-LMACEJJS.js} +6 -5
- package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
- package/dist/{status-6WNXX2O7.js → status-F4THTJJK.js} +24 -18
- package/dist/{sync-cmd-YONVGUYS.js → sync-cmd-M6JA3OEE.js} +5 -4
- package/dist/{watch-HHKAF26L.js → watch-NB5TGPEZ.js} +5 -4
- package/dist/{work-claim-cmd-LXBZTMJA.js → work-claim-cmd-UHMWOS3G.js} +13 -6
- package/dist/{work-cmd-OVNF44FY.js → work-cmd-6UMCFTAW.js} +9 -3
- package/dist/{work-comment-cmd-6DGQNTAH.js → work-comment-cmd-E4LEUIXZ.js} +9 -3
- package/dist/{work-open-pr-cmd-FCZHFHZS.js → work-open-pr-cmd-OPZWGSEQ.js} +10 -4
- package/dist/{work-pr-cmd-QJJVHZDC.js → work-pr-cmd-IDIAFFRU.js} +9 -2
- package/package.json +1 -1
- package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
- package/dist/chunk-7DBI6SNT.js +0 -55
- package/dist/config-HL2AIR3K.js +0 -9
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { RESERVED_EMIT_NAMES } from './chunk-YNJEOGDL.js';
|
|
3
4
|
import { __commonJS, __require, __toESM } from './chunk-Z5GEFMRW.js';
|
|
4
5
|
|
|
5
6
|
createRequire(import.meta.url);
|
|
@@ -7163,7 +7164,7 @@ var require_public_api = __commonJS({
|
|
|
7163
7164
|
}
|
|
7164
7165
|
return doc;
|
|
7165
7166
|
}
|
|
7166
|
-
function
|
|
7167
|
+
function parse2(src, reviver, options) {
|
|
7167
7168
|
let _reviver = void 0;
|
|
7168
7169
|
if (typeof reviver === "function") {
|
|
7169
7170
|
_reviver = reviver;
|
|
@@ -7204,7 +7205,7 @@ var require_public_api = __commonJS({
|
|
|
7204
7205
|
return value.toString(options);
|
|
7205
7206
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
7206
7207
|
}
|
|
7207
|
-
exports$1.parse =
|
|
7208
|
+
exports$1.parse = parse2;
|
|
7208
7209
|
exports$1.parseAllDocuments = parseAllDocuments;
|
|
7209
7210
|
exports$1.parseDocument = parseDocument2;
|
|
7210
7211
|
exports$1.stringify = stringify;
|
|
@@ -7286,8 +7287,8 @@ function classifyOnePath(rawPath) {
|
|
|
7286
7287
|
const segs = path.split("/");
|
|
7287
7288
|
const outerlayerIndex = segs.indexOf(OUTERLAYER_DIR);
|
|
7288
7289
|
if (outerlayerIndex === -1) {
|
|
7289
|
-
const
|
|
7290
|
-
if (
|
|
7290
|
+
const base2 = segs[segs.length - 1];
|
|
7291
|
+
if (base2 === "AGENTS.md" || base2 === "CLAUDE.md") {
|
|
7291
7292
|
const scopePath2 = segs.slice(0, -1).join("/");
|
|
7292
7293
|
return { type: "entry", entry: { path, kind: "external-instructions", scopePath: scopePath2 } };
|
|
7293
7294
|
}
|
|
@@ -7974,5 +7975,554 @@ function normalizeRepoName(repo) {
|
|
|
7974
7975
|
}
|
|
7975
7976
|
var CONTEXT_PIN_FILENAME = ".outerlayer-pin.json";
|
|
7976
7977
|
var CONTEXT_PIN_SCHEMA_VERSION = 1;
|
|
7978
|
+
var base = {
|
|
7979
|
+
whenPaths: null,
|
|
7980
|
+
whenIssueType: null,
|
|
7981
|
+
whenIssueLabels: null,
|
|
7982
|
+
needs: [],
|
|
7983
|
+
declaresEmit: null,
|
|
7984
|
+
level: "warn"
|
|
7985
|
+
};
|
|
7986
|
+
var REGISTRY_VALIDATORS = [
|
|
7987
|
+
{
|
|
7988
|
+
...base,
|
|
7989
|
+
id: "code-review-ran",
|
|
7990
|
+
name: "Code review report",
|
|
7991
|
+
row: "A code review report is attached to this change",
|
|
7992
|
+
requireAny: [{ type: "artifact", kind: "report", for: "code-review-ran" }]
|
|
7993
|
+
}
|
|
7994
|
+
];
|
|
7995
|
+
var MAX_COMMAND_LENGTH = 2e3;
|
|
7996
|
+
var RECOMMENDED_EXTENDS = "outerlayer:recommended@v1";
|
|
7997
|
+
var FREED_BUILTINS = /* @__PURE__ */ new Set(["ci-passed", "acceptance-criteria"]);
|
|
7998
|
+
var RETIRED_BUILTINS = /* @__PURE__ */ new Set([
|
|
7999
|
+
"red-then-green",
|
|
8000
|
+
"no-test-tampering",
|
|
8001
|
+
"tests-after-last-edit",
|
|
8002
|
+
"commits-from-sessions"
|
|
8003
|
+
]);
|
|
8004
|
+
var SLUG_PATTERN2 = /^[a-z][a-z0-9._-]{0,63}$/;
|
|
8005
|
+
var CONTROL_CHARS = /\p{Cc}/u;
|
|
8006
|
+
function inlineText(raw, max = 80) {
|
|
8007
|
+
const flat = raw.replace(/\p{Cc}+/gu, " ");
|
|
8008
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
8009
|
+
}
|
|
8010
|
+
var MAX_ROW_LENGTH = 200;
|
|
8011
|
+
var MAX_NAME_LENGTH = 40;
|
|
8012
|
+
var MAX_GLOB_LENGTH = 200;
|
|
8013
|
+
var MAX_WHEN_PATHS = 20;
|
|
8014
|
+
var NEEDS_ALIASES = {
|
|
8015
|
+
"tool-calls.commands": "commands",
|
|
8016
|
+
"tool-calls.edits": "edits",
|
|
8017
|
+
commands: "commands",
|
|
8018
|
+
edits: "edits"
|
|
8019
|
+
};
|
|
8020
|
+
function isRecord(value) {
|
|
8021
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8022
|
+
}
|
|
8023
|
+
function parseLevel(value) {
|
|
8024
|
+
return value === "warn" || value === "info" || value === "off" ? value : null;
|
|
8025
|
+
}
|
|
8026
|
+
function parseMergeGate(value) {
|
|
8027
|
+
return value === "none" || value === "on-flag" || value === "on-unverifiable" ? value : null;
|
|
8028
|
+
}
|
|
8029
|
+
function parseReview(value) {
|
|
8030
|
+
return value === "required" || value === "optional" ? value : null;
|
|
8031
|
+
}
|
|
8032
|
+
function parsePolicyFile(file) {
|
|
8033
|
+
const problems = [];
|
|
8034
|
+
const fail = (problem) => {
|
|
8035
|
+
problems.push({ file: file.path, problem });
|
|
8036
|
+
return { extendsOk: false, overrides: {}, mergeGate: null, review: null, testProof: null, criteriaMissing: null, problems };
|
|
8037
|
+
};
|
|
8038
|
+
let root;
|
|
8039
|
+
try {
|
|
8040
|
+
root = (0, import_yaml.parse)(file.content);
|
|
8041
|
+
} catch (error) {
|
|
8042
|
+
return fail(`not valid YAML \u2014 ${error instanceof Error ? error.message : String(error)}`);
|
|
8043
|
+
}
|
|
8044
|
+
if (!isRecord(root)) return fail("must be a YAML mapping");
|
|
8045
|
+
for (const key of Object.keys(root)) {
|
|
8046
|
+
if (key !== "extends" && key !== "validators" && key !== "merge_gate" && key !== "review" && key !== "build" && key !== "criteria") {
|
|
8047
|
+
return fail(`unknown key "${inlineText(key)}"`);
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
if (root["build"] !== void 0) {
|
|
8051
|
+
const build = root["build"];
|
|
8052
|
+
if (!isRecord(build)) return fail("build must be a mapping");
|
|
8053
|
+
for (const key of Object.keys(build)) {
|
|
8054
|
+
if (key !== "workflows") return fail(`unknown key "build.${inlineText(key)}"`);
|
|
8055
|
+
}
|
|
8056
|
+
const workflows = build["workflows"];
|
|
8057
|
+
if (workflows !== void 0 && workflows !== "allow" && workflows !== "deny") {
|
|
8058
|
+
return fail(`build.workflows has value "${inlineText(String(workflows))}" \u2014 use allow or deny`);
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
let testProof = null;
|
|
8062
|
+
let criteriaMissing = null;
|
|
8063
|
+
if (root["criteria"] !== void 0) {
|
|
8064
|
+
const criteria = root["criteria"];
|
|
8065
|
+
if (!isRecord(criteria)) return fail("criteria must be a mapping");
|
|
8066
|
+
for (const key of Object.keys(criteria)) {
|
|
8067
|
+
if (key !== "replace" && key !== "test_proof" && key !== "missing") {
|
|
8068
|
+
return fail(`unknown key "criteria.${inlineText(key)}"`);
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
const rawTestProof = criteria["test_proof"];
|
|
8072
|
+
if (rawTestProof !== void 0) {
|
|
8073
|
+
if (rawTestProof !== "any" && rawTestProof !== "ci") {
|
|
8074
|
+
return fail(`criteria.test_proof has value "${inlineText(String(rawTestProof))}" \u2014 use any or ci`);
|
|
8075
|
+
}
|
|
8076
|
+
testProof = rawTestProof;
|
|
8077
|
+
}
|
|
8078
|
+
const rawMissing = criteria["missing"];
|
|
8079
|
+
if (rawMissing !== void 0) {
|
|
8080
|
+
const level = parseLevel(rawMissing);
|
|
8081
|
+
if (level === null) {
|
|
8082
|
+
return fail(`criteria.missing has value "${inlineText(String(rawMissing))}" \u2014 use warn, info, or off`);
|
|
8083
|
+
}
|
|
8084
|
+
criteriaMissing = level;
|
|
8085
|
+
}
|
|
8086
|
+
const replace = criteria["replace"];
|
|
8087
|
+
if (replace !== void 0 && replace !== "people" && replace !== "anyone") {
|
|
8088
|
+
return fail(`criteria.replace has value "${inlineText(String(replace))}" \u2014 use people or anyone`);
|
|
8089
|
+
}
|
|
8090
|
+
}
|
|
8091
|
+
let mergeGate = null;
|
|
8092
|
+
if (root["merge_gate"] !== void 0) {
|
|
8093
|
+
mergeGate = parseMergeGate(root["merge_gate"]);
|
|
8094
|
+
if (mergeGate === null) {
|
|
8095
|
+
return fail(
|
|
8096
|
+
`merge_gate has value "${inlineText(String(root["merge_gate"]))}" \u2014 use none, on-flag, or on-unverifiable`
|
|
8097
|
+
);
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
let review = null;
|
|
8101
|
+
if (root["review"] !== void 0) {
|
|
8102
|
+
review = parseReview(root["review"]);
|
|
8103
|
+
if (review === null) {
|
|
8104
|
+
return fail(`review has value "${inlineText(String(root["review"]))}" \u2014 use required or optional`);
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
if (root["extends"] === void 0) {
|
|
8108
|
+
return fail(`missing "extends" \u2014 adopt the registry with extends: ${RECOMMENDED_EXTENDS}`);
|
|
8109
|
+
}
|
|
8110
|
+
if (root["extends"] !== RECOMMENDED_EXTENDS) {
|
|
8111
|
+
return fail(
|
|
8112
|
+
`unknown extends "${inlineText(String(root["extends"]))}" \u2014 the only supported registry is ${RECOMMENDED_EXTENDS}`
|
|
8113
|
+
);
|
|
8114
|
+
}
|
|
8115
|
+
const overrides = {};
|
|
8116
|
+
const validators = root["validators"];
|
|
8117
|
+
if (validators !== void 0) {
|
|
8118
|
+
if (!isRecord(validators)) return fail(`"validators" must be a mapping of id to level`);
|
|
8119
|
+
for (const [id, rawLevel] of Object.entries(validators)) {
|
|
8120
|
+
const level = parseLevel(rawLevel);
|
|
8121
|
+
if (level === null) {
|
|
8122
|
+
return fail(`validator "${inlineText(id)}" has level "${inlineText(String(rawLevel))}" \u2014 use warn, info, or off`);
|
|
8123
|
+
}
|
|
8124
|
+
overrides[id] = level;
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
return { extendsOk: true, overrides, mergeGate, review, testProof, criteriaMissing, problems };
|
|
8128
|
+
}
|
|
8129
|
+
function parseSessionRan(value, file, problems) {
|
|
8130
|
+
var _a;
|
|
8131
|
+
if (!isRecord(value)) {
|
|
8132
|
+
problems.push({ file, problem: `"session.ran" must be a mapping with a "command"` });
|
|
8133
|
+
return null;
|
|
8134
|
+
}
|
|
8135
|
+
for (const key of Object.keys(value)) {
|
|
8136
|
+
if (key !== "command" && key !== "status") {
|
|
8137
|
+
problems.push({ file, problem: `"session.ran" has unknown key "${inlineText(key)}"` });
|
|
8138
|
+
return null;
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
const command = value["command"];
|
|
8142
|
+
if (typeof command !== "string" || command.trim() === "") {
|
|
8143
|
+
problems.push({ file, problem: `"session.ran" needs a non-empty "command"` });
|
|
8144
|
+
return null;
|
|
8145
|
+
}
|
|
8146
|
+
const status = (_a = value["status"]) != null ? _a : "ok";
|
|
8147
|
+
if (status !== "ok" && status !== "error") {
|
|
8148
|
+
problems.push({ file, problem: `"session.ran" status must be ok or error` });
|
|
8149
|
+
return null;
|
|
8150
|
+
}
|
|
8151
|
+
return { type: "session-ran", command: command.trim(), status };
|
|
8152
|
+
}
|
|
8153
|
+
function parseRequirementAlt(value, file, problems) {
|
|
8154
|
+
if (!isRecord(value)) {
|
|
8155
|
+
problems.push({ file, problem: `each requirement must be a single-key mapping` });
|
|
8156
|
+
return null;
|
|
8157
|
+
}
|
|
8158
|
+
const keys = Object.keys(value);
|
|
8159
|
+
if (keys.length !== 1) {
|
|
8160
|
+
problems.push({
|
|
8161
|
+
file,
|
|
8162
|
+
problem: `a requirement names exactly one of session.ran, validator, emitted`
|
|
8163
|
+
});
|
|
8164
|
+
return null;
|
|
8165
|
+
}
|
|
8166
|
+
const key = keys[0];
|
|
8167
|
+
if (key === "session.ran") return parseSessionRan(value[key], file, problems);
|
|
8168
|
+
if (key === "validator") {
|
|
8169
|
+
const id = value[key];
|
|
8170
|
+
if (typeof id !== "string" || !SLUG_PATTERN2.test(id)) {
|
|
8171
|
+
problems.push({ file, problem: `"validator" must name a validator id` });
|
|
8172
|
+
return null;
|
|
8173
|
+
}
|
|
8174
|
+
return { type: "validator", id };
|
|
8175
|
+
}
|
|
8176
|
+
if (key === "emitted") {
|
|
8177
|
+
const name = value[key];
|
|
8178
|
+
if (typeof name !== "string" || !SLUG_PATTERN2.test(name)) {
|
|
8179
|
+
problems.push({ file, problem: `"emitted" must name an emit (id characters only)` });
|
|
8180
|
+
return null;
|
|
8181
|
+
}
|
|
8182
|
+
return { type: "emitted", name };
|
|
8183
|
+
}
|
|
8184
|
+
problems.push({ file, problem: `unknown requirement "${key}"` });
|
|
8185
|
+
return null;
|
|
8186
|
+
}
|
|
8187
|
+
function parseRequire(value, file, problems) {
|
|
8188
|
+
if (isRecord(value) && "any" in value) {
|
|
8189
|
+
if (Object.keys(value).length !== 1 || !Array.isArray(value["any"]) || value["any"].length === 0) {
|
|
8190
|
+
problems.push({ file, problem: `"require.any" must be a non-empty list of requirements` });
|
|
8191
|
+
return null;
|
|
8192
|
+
}
|
|
8193
|
+
const alts = [];
|
|
8194
|
+
for (const entry of value["any"]) {
|
|
8195
|
+
const alt = parseRequirementAlt(entry, file, problems);
|
|
8196
|
+
if (alt === null) return null;
|
|
8197
|
+
alts.push(alt);
|
|
8198
|
+
}
|
|
8199
|
+
return alts;
|
|
8200
|
+
}
|
|
8201
|
+
const single = parseRequirementAlt(value, file, problems);
|
|
8202
|
+
return single === null ? null : [single];
|
|
8203
|
+
}
|
|
8204
|
+
function parseNeeds(value, file, problems) {
|
|
8205
|
+
if (value === void 0) return [];
|
|
8206
|
+
if (!Array.isArray(value)) {
|
|
8207
|
+
problems.push({ file, problem: `"needs" must be a list of fact families` });
|
|
8208
|
+
return null;
|
|
8209
|
+
}
|
|
8210
|
+
const families = [];
|
|
8211
|
+
for (const entry of value) {
|
|
8212
|
+
const family = typeof entry === "string" ? NEEDS_ALIASES[entry] : void 0;
|
|
8213
|
+
if (family === void 0) {
|
|
8214
|
+
problems.push({ file, problem: `unknown fact family "${inlineText(String(entry))}" in "needs"` });
|
|
8215
|
+
return null;
|
|
8216
|
+
}
|
|
8217
|
+
families.push(family);
|
|
8218
|
+
}
|
|
8219
|
+
return families;
|
|
8220
|
+
}
|
|
8221
|
+
var CUSTOM_KEYS = /* @__PURE__ */ new Set([
|
|
8222
|
+
"id",
|
|
8223
|
+
"kind",
|
|
8224
|
+
"name",
|
|
8225
|
+
"row",
|
|
8226
|
+
"level",
|
|
8227
|
+
"when",
|
|
8228
|
+
"require",
|
|
8229
|
+
"run",
|
|
8230
|
+
"needs"
|
|
8231
|
+
]);
|
|
8232
|
+
function parseCustomFile(file, problems) {
|
|
8233
|
+
const fail = (problem) => {
|
|
8234
|
+
problems.push({ file: file.path, problem });
|
|
8235
|
+
return null;
|
|
8236
|
+
};
|
|
8237
|
+
let root;
|
|
8238
|
+
try {
|
|
8239
|
+
root = (0, import_yaml.parse)(file.content);
|
|
8240
|
+
} catch (error) {
|
|
8241
|
+
return fail(`not valid YAML \u2014 ${error instanceof Error ? error.message : String(error)}`);
|
|
8242
|
+
}
|
|
8243
|
+
if (!isRecord(root)) return fail("must be a YAML mapping");
|
|
8244
|
+
for (const key of Object.keys(root)) {
|
|
8245
|
+
if (!CUSTOM_KEYS.has(key)) return fail(`unknown key "${inlineText(key)}"`);
|
|
8246
|
+
}
|
|
8247
|
+
const id = root["id"];
|
|
8248
|
+
if (typeof id !== "string" || !SLUG_PATTERN2.test(id)) {
|
|
8249
|
+
return fail(`"id" must be lowercase id characters (got ${inlineText(JSON.stringify(root["id"]))})`);
|
|
8250
|
+
}
|
|
8251
|
+
const kind = root["kind"];
|
|
8252
|
+
if (kind !== "validation" && kind !== "signal") {
|
|
8253
|
+
return fail(`"${id}" has kind "${inlineText(String(kind))}" \u2014 use validation or signal`);
|
|
8254
|
+
}
|
|
8255
|
+
if (kind === "signal") return { signalId: id };
|
|
8256
|
+
const rawRow = root["row"];
|
|
8257
|
+
if (typeof rawRow !== "string" || rawRow.trim() === "") {
|
|
8258
|
+
return fail(`"${id}" needs a "row" \u2014 the sentence its result renders as`);
|
|
8259
|
+
}
|
|
8260
|
+
const row = rawRow.trim();
|
|
8261
|
+
if (CONTROL_CHARS.test(row) || row.length > MAX_ROW_LENGTH) {
|
|
8262
|
+
return fail(`"${id}" row must be a single line of at most ${MAX_ROW_LENGTH} characters`);
|
|
8263
|
+
}
|
|
8264
|
+
const rawName = root["name"];
|
|
8265
|
+
let name = null;
|
|
8266
|
+
if (rawName !== void 0) {
|
|
8267
|
+
if (typeof rawName !== "string") {
|
|
8268
|
+
return fail(`"${id}" name must be one short line`);
|
|
8269
|
+
}
|
|
8270
|
+
const trimmedName = rawName.trim();
|
|
8271
|
+
if (trimmedName === "" || CONTROL_CHARS.test(trimmedName) || trimmedName.length > MAX_NAME_LENGTH) {
|
|
8272
|
+
return fail(`"${id}" name must be one short line`);
|
|
8273
|
+
}
|
|
8274
|
+
name = trimmedName;
|
|
8275
|
+
}
|
|
8276
|
+
const level = root["level"] === void 0 ? "warn" : parseLevel(root["level"]);
|
|
8277
|
+
if (level === null) {
|
|
8278
|
+
return fail(`"${id}" has level "${inlineText(String(root["level"]))}" \u2014 use warn, info, or off`);
|
|
8279
|
+
}
|
|
8280
|
+
let whenPaths = null;
|
|
8281
|
+
let whenIssueType = null;
|
|
8282
|
+
let whenIssueLabels = null;
|
|
8283
|
+
if (root["when"] !== void 0) {
|
|
8284
|
+
const when = root["when"];
|
|
8285
|
+
if (!isRecord(when) || Object.keys(when).some(
|
|
8286
|
+
(key) => key !== "paths" && key !== "issue.type" && key !== "issue.labels"
|
|
8287
|
+
) || Object.keys(when).length === 0) {
|
|
8288
|
+
return fail(
|
|
8289
|
+
`"${id}" when: only "paths", "issue.type", and "issue.labels" scoping is supported`
|
|
8290
|
+
);
|
|
8291
|
+
}
|
|
8292
|
+
const paths = when["paths"];
|
|
8293
|
+
if (paths !== void 0) {
|
|
8294
|
+
if (!Array.isArray(paths) || paths.length === 0 || paths.some((p) => typeof p !== "string" || p.trim() === "")) {
|
|
8295
|
+
return fail(`"${id}" when.paths must be a non-empty list of path globs`);
|
|
8296
|
+
}
|
|
8297
|
+
if (paths.length > MAX_WHEN_PATHS) {
|
|
8298
|
+
return fail(`"${id}" when.paths lists ${paths.length} globs \u2014 the cap is ${MAX_WHEN_PATHS}`);
|
|
8299
|
+
}
|
|
8300
|
+
const oversized = paths.find((p) => p.length > MAX_GLOB_LENGTH);
|
|
8301
|
+
if (oversized !== void 0) {
|
|
8302
|
+
return fail(
|
|
8303
|
+
`"${id}" when.paths has a ${oversized.length}-character glob \u2014 the cap is ${MAX_GLOB_LENGTH}`
|
|
8304
|
+
);
|
|
8305
|
+
}
|
|
8306
|
+
whenPaths = paths.map((p) => p.trim());
|
|
8307
|
+
}
|
|
8308
|
+
const issueType = when["issue.type"];
|
|
8309
|
+
if (issueType !== void 0) {
|
|
8310
|
+
if (typeof issueType !== "string" || issueType.trim() === "") {
|
|
8311
|
+
return fail(`"${id}" when.issue.type must be a non-empty string`);
|
|
8312
|
+
}
|
|
8313
|
+
whenIssueType = issueType.trim();
|
|
8314
|
+
}
|
|
8315
|
+
const issueLabels = when["issue.labels"];
|
|
8316
|
+
if (issueLabels !== void 0) {
|
|
8317
|
+
if (!Array.isArray(issueLabels) || issueLabels.length === 0 || issueLabels.some((label) => typeof label !== "string" || label.trim() === "")) {
|
|
8318
|
+
return fail(`"${id}" when.issue.labels must be a non-empty list of label names`);
|
|
8319
|
+
}
|
|
8320
|
+
whenIssueLabels = issueLabels.map((label) => label.trim());
|
|
8321
|
+
}
|
|
8322
|
+
}
|
|
8323
|
+
const hasRequire = root["require"] !== void 0;
|
|
8324
|
+
const hasRun = root["run"] !== void 0;
|
|
8325
|
+
if (hasRequire === hasRun) {
|
|
8326
|
+
return fail(`"${id}" must declare exactly one of "require" or "run"`);
|
|
8327
|
+
}
|
|
8328
|
+
let requireAny;
|
|
8329
|
+
let declaresEmit = null;
|
|
8330
|
+
let runWhere;
|
|
8331
|
+
let command;
|
|
8332
|
+
if (hasRun) {
|
|
8333
|
+
const run = root["run"];
|
|
8334
|
+
if (!isRecord(run) || Object.keys(run).some((key) => key !== "where" && key !== "emit" && key !== "command")) {
|
|
8335
|
+
return fail(`"${id}" run: must be a mapping of "where", "emit" and, for where: host, "command"`);
|
|
8336
|
+
}
|
|
8337
|
+
const where = run["where"];
|
|
8338
|
+
if (where !== "ci" && where !== "host") {
|
|
8339
|
+
return fail(`"${id}" run.where must be ci or host \u2014 the engine never executes customer code, so a host check runs on the factory host`);
|
|
8340
|
+
}
|
|
8341
|
+
const emit = run["emit"];
|
|
8342
|
+
if (typeof emit !== "string" || !SLUG_PATTERN2.test(emit)) {
|
|
8343
|
+
return fail(`"${id}" run.emit must name the emit (id characters only)`);
|
|
8344
|
+
}
|
|
8345
|
+
if (RESERVED_EMIT_NAMES.includes(emit)) {
|
|
8346
|
+
return fail(
|
|
8347
|
+
`"${id}" run.emit "${emit}" is reserved \u2014 \`outerlayer emit ${emit}\` is a subcommand, so a check cannot be emitted under that name`
|
|
8348
|
+
);
|
|
8349
|
+
}
|
|
8350
|
+
if (where === "ci" && run["command"] !== void 0) {
|
|
8351
|
+
return fail(`"${id}" run.command is only for run.where: host \u2014 a where: ci check runs in your own CI`);
|
|
8352
|
+
}
|
|
8353
|
+
if (where === "host") {
|
|
8354
|
+
const rawCommand = run["command"];
|
|
8355
|
+
if (rawCommand === void 0) {
|
|
8356
|
+
return fail(`"${id}" run.where: host needs a "command" \u2014 the shell line the host runs`);
|
|
8357
|
+
}
|
|
8358
|
+
if (typeof rawCommand !== "string" || rawCommand.trim() === "" || rawCommand.length > MAX_COMMAND_LENGTH) {
|
|
8359
|
+
return fail(`"${id}" run.command must be a non-empty string of at most ${MAX_COMMAND_LENGTH} characters`);
|
|
8360
|
+
}
|
|
8361
|
+
command = rawCommand.trim();
|
|
8362
|
+
runWhere = "host";
|
|
8363
|
+
}
|
|
8364
|
+
declaresEmit = emit;
|
|
8365
|
+
requireAny = [where === "host" ? { type: "emitted", name: emit, recorder: "host" } : { type: "emitted", name: emit }];
|
|
8366
|
+
} else {
|
|
8367
|
+
const parsed = parseRequire(root["require"], file.path, problems);
|
|
8368
|
+
if (parsed === null) return null;
|
|
8369
|
+
requireAny = parsed;
|
|
8370
|
+
}
|
|
8371
|
+
const declaredNeeds = parseNeeds(root["needs"], file.path, problems);
|
|
8372
|
+
if (declaredNeeds === null) return null;
|
|
8373
|
+
return {
|
|
8374
|
+
def: {
|
|
8375
|
+
id,
|
|
8376
|
+
name,
|
|
8377
|
+
row: row.trim(),
|
|
8378
|
+
level,
|
|
8379
|
+
whenPaths,
|
|
8380
|
+
whenIssueType,
|
|
8381
|
+
whenIssueLabels,
|
|
8382
|
+
requireAny,
|
|
8383
|
+
// Declared needs only — the author's own statement of what the rule
|
|
8384
|
+
// reads. A session.ran alternative is guarded per-alternative at
|
|
8385
|
+
// evaluation regardless, so an uncaptured command channel can never
|
|
8386
|
+
// silently pass; deriving it here would additionally blind an `any:`
|
|
8387
|
+
// whose OTHER alternative (an emitted result) could still answer.
|
|
8388
|
+
needs: [...new Set(declaredNeeds)].sort(),
|
|
8389
|
+
declaresEmit,
|
|
8390
|
+
...runWhere === void 0 ? {} : { runWhere },
|
|
8391
|
+
...command === void 0 ? {} : { command }
|
|
8392
|
+
},
|
|
8393
|
+
file: file.path,
|
|
8394
|
+
validatorRefs: requireAny.flatMap((alt) => alt.type === "validator" ? [alt.id] : []),
|
|
8395
|
+
emittedRefs: requireAny.flatMap((alt) => alt.type === "emitted" ? [alt.name] : [])
|
|
8396
|
+
};
|
|
8397
|
+
}
|
|
8398
|
+
function resolveReferences(parsed, signalIds, problems) {
|
|
8399
|
+
let surviving = parsed;
|
|
8400
|
+
for (; ; ) {
|
|
8401
|
+
const ids = new Set(surviving.map((p) => p.def.id));
|
|
8402
|
+
const emits = new Set(
|
|
8403
|
+
surviving.flatMap((p) => p.def.declaresEmit === null ? [] : [p.def.declaresEmit])
|
|
8404
|
+
);
|
|
8405
|
+
const next = surviving.filter((candidate) => {
|
|
8406
|
+
for (const ref of candidate.validatorRefs) {
|
|
8407
|
+
if (ref === candidate.def.id || !ids.has(ref) || signalIds.has(ref)) {
|
|
8408
|
+
problems.push({
|
|
8409
|
+
file: candidate.file,
|
|
8410
|
+
problem: `"${candidate.def.id}" requires validator "${ref}" \u2014 ${signalIds.has(ref) ? "a signal can never satisfy a validation" : RETIRED_BUILTINS.has(ref) ? "that built-in check was retired" : "no such validator exists"}`
|
|
8411
|
+
});
|
|
8412
|
+
return false;
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
for (const name of candidate.emittedRefs) {
|
|
8416
|
+
if (!emits.has(name)) {
|
|
8417
|
+
problems.push({
|
|
8418
|
+
file: candidate.file,
|
|
8419
|
+
problem: `"${candidate.def.id}" requires emitted "${name}" but no validator declares emit: ${name}`
|
|
8420
|
+
});
|
|
8421
|
+
return false;
|
|
8422
|
+
}
|
|
8423
|
+
}
|
|
8424
|
+
return true;
|
|
8425
|
+
});
|
|
8426
|
+
if (next.length === surviving.length) return dropCycles(next, problems);
|
|
8427
|
+
surviving = next;
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
function dropCycles(parsed, problems) {
|
|
8431
|
+
const byId = new Map(parsed.map((p) => [p.def.id, p]));
|
|
8432
|
+
const state = /* @__PURE__ */ new Map();
|
|
8433
|
+
const cyclic = /* @__PURE__ */ new Set();
|
|
8434
|
+
const visit = (id, stack) => {
|
|
8435
|
+
const entry = byId.get(id);
|
|
8436
|
+
if (!entry || state.get(id) === "done") return;
|
|
8437
|
+
if (state.get(id) === "visiting") {
|
|
8438
|
+
for (const member of stack.slice(stack.indexOf(id))) cyclic.add(member);
|
|
8439
|
+
return;
|
|
8440
|
+
}
|
|
8441
|
+
state.set(id, "visiting");
|
|
8442
|
+
for (const ref of entry.validatorRefs) visit(ref, [...stack, id]);
|
|
8443
|
+
state.set(id, "done");
|
|
8444
|
+
};
|
|
8445
|
+
for (const p of parsed) visit(p.def.id, []);
|
|
8446
|
+
return parsed.filter((p) => {
|
|
8447
|
+
if (!cyclic.has(p.def.id)) return true;
|
|
8448
|
+
problems.push({
|
|
8449
|
+
file: p.file,
|
|
8450
|
+
problem: `"${p.def.id}" is part of a validator-reference cycle and cannot be evaluated`
|
|
8451
|
+
});
|
|
8452
|
+
return false;
|
|
8453
|
+
});
|
|
8454
|
+
}
|
|
8455
|
+
function parsePolicy(policyFile, validatorFiles) {
|
|
8456
|
+
var _a, _b, _c, _d, _e;
|
|
8457
|
+
const problems = [];
|
|
8458
|
+
const policy = policyFile ? parsePolicyFile(policyFile) : {
|
|
8459
|
+
extendsOk: true,
|
|
8460
|
+
overrides: {},
|
|
8461
|
+
mergeGate: null,
|
|
8462
|
+
review: null,
|
|
8463
|
+
testProof: null,
|
|
8464
|
+
criteriaMissing: null,
|
|
8465
|
+
problems: []
|
|
8466
|
+
};
|
|
8467
|
+
problems.push(...policy.problems);
|
|
8468
|
+
const mergeGate = (_a = policy.mergeGate) != null ? _a : "none";
|
|
8469
|
+
const review = (_b = policy.review) != null ? _b : "required";
|
|
8470
|
+
const testProof = (_c = policy.testProof) != null ? _c : "any";
|
|
8471
|
+
const criteriaMissing = (_d = policy.criteriaMissing) != null ? _d : policyFile !== null ? "warn" : "off";
|
|
8472
|
+
const parsed = [];
|
|
8473
|
+
const signalIds = /* @__PURE__ */ new Set();
|
|
8474
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
8475
|
+
for (const file of [...validatorFiles].sort((a, b) => a.path < b.path ? -1 : 1)) {
|
|
8476
|
+
const result = parseCustomFile(file, problems);
|
|
8477
|
+
if (result === null) continue;
|
|
8478
|
+
const id = "signalId" in result ? result.signalId : result.def.id;
|
|
8479
|
+
const collidesWithBuiltin = RETIRED_BUILTINS.has(id);
|
|
8480
|
+
if (seenIds.has(id) || collidesWithBuiltin) {
|
|
8481
|
+
problems.push({
|
|
8482
|
+
file: file.path,
|
|
8483
|
+
problem: collidesWithBuiltin ? `"${id}" collides with a built-in validator id` : `duplicate validator id "${id}"`
|
|
8484
|
+
});
|
|
8485
|
+
continue;
|
|
8486
|
+
}
|
|
8487
|
+
seenIds.add(id);
|
|
8488
|
+
if ("signalId" in result) signalIds.add(id);
|
|
8489
|
+
else parsed.push(result);
|
|
8490
|
+
}
|
|
8491
|
+
const emitOwners = /* @__PURE__ */ new Map();
|
|
8492
|
+
const deduped = parsed.filter((p) => {
|
|
8493
|
+
if (p.def.declaresEmit === null) return true;
|
|
8494
|
+
const owner = emitOwners.get(p.def.declaresEmit);
|
|
8495
|
+
if (owner === void 0) {
|
|
8496
|
+
emitOwners.set(p.def.declaresEmit, p.def.id);
|
|
8497
|
+
return true;
|
|
8498
|
+
}
|
|
8499
|
+
problems.push({
|
|
8500
|
+
file: p.file,
|
|
8501
|
+
problem: `"${p.def.id}" declares emit "${p.def.declaresEmit}" already declared by "${owner}"`
|
|
8502
|
+
});
|
|
8503
|
+
return false;
|
|
8504
|
+
});
|
|
8505
|
+
const resolved = resolveReferences(deduped, signalIds, problems);
|
|
8506
|
+
const registry = policyFile !== null && policy.extendsOk ? REGISTRY_VALIDATORS.filter((def) => !seenIds.has(def.id)) : [];
|
|
8507
|
+
const sorted = [...resolved.map((p) => p.def), ...registry].sort((a, b) => a.id < b.id ? -1 : 1);
|
|
8508
|
+
const levels = {};
|
|
8509
|
+
for (const custom of sorted) levels[custom.id] = custom.level;
|
|
8510
|
+
for (const [id, level] of Object.entries(policy.overrides)) {
|
|
8511
|
+
if (RETIRED_BUILTINS.has(id)) continue;
|
|
8512
|
+
if (FREED_BUILTINS.has(id) && !(id in levels)) continue;
|
|
8513
|
+
if (!(id in levels) && !signalIds.has(id)) {
|
|
8514
|
+
problems.push({
|
|
8515
|
+
file: (_e = policyFile == null ? void 0 : policyFile.path) != null ? _e : ".outerlayer/policy.yaml",
|
|
8516
|
+
problem: `policy levels unknown validator "${id}"`
|
|
8517
|
+
});
|
|
8518
|
+
continue;
|
|
8519
|
+
}
|
|
8520
|
+
if (!signalIds.has(id)) levels[id] = level;
|
|
8521
|
+
}
|
|
8522
|
+
const customs = sorted.map(
|
|
8523
|
+
(def) => levels[def.id] === def.level ? def : { ...def, level: levels[def.id] }
|
|
8524
|
+
);
|
|
8525
|
+
return { levels, customs, mergeGate, review, testProof, criteriaMissing, problems };
|
|
8526
|
+
}
|
|
7977
8527
|
|
|
7978
|
-
export { CONTEXT_PIN_FILENAME, CONTEXT_PIN_SCHEMA_VERSION, CONTEXT_SOURCE_CONFIG_PATH, classifyTree, emitTree, generatedHeaderSource, normalizePath, normalizeRepoName, parseGoverns, parseOuterlayerConfig, splitRawFile };
|
|
8528
|
+
export { CONTEXT_PIN_FILENAME, CONTEXT_PIN_SCHEMA_VERSION, CONTEXT_SOURCE_CONFIG_PATH, classifyTree, emitTree, generatedHeaderSource, normalizePath, normalizeRepoName, parseGoverns, parseOuterlayerConfig, parsePolicy, splitRawFile };
|