@cejel/cejel 0.7.0-rc.2 → 0.7.0-rc.3
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.
|
@@ -5274,6 +5274,8 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5274
5274
|
const rootPublishingDetails = /* @__PURE__ */ new Map();
|
|
5275
5275
|
const checkedPublishingDetails = /* @__PURE__ */ new Map();
|
|
5276
5276
|
const rootCheckedPublishingDetails = /* @__PURE__ */ new Map();
|
|
5277
|
+
const uncreditedLines = /* @__PURE__ */ new Map();
|
|
5278
|
+
const rootUncreditedLines = /* @__PURE__ */ new Map();
|
|
5277
5279
|
const uncreditedWorkflows = useV27FalseAssuranceClosure ? withContentReadSignals("B3", ["ci_script_depth", "default_branch_ci_depth"], () => {
|
|
5278
5280
|
const inventory = ownPublishOnlyInventory(repoFiles);
|
|
5279
5281
|
return publishingWorkflowReasons(
|
|
@@ -5284,7 +5286,8 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5284
5286
|
checkedPublishingDetails,
|
|
5285
5287
|
// Eighth pass (#659): v17's test-file list, the one A1 counts, read only when a runner is
|
|
5286
5288
|
// given a positional word (testPathReader reads it once).
|
|
5287
|
-
checkReading(repoPath, inventory, () => findConcreteTestFiles(repoPath, repoFiles, WITAN_RUBRIC_VERSION_V17))
|
|
5289
|
+
checkReading(repoPath, inventory, () => findConcreteTestFiles(repoPath, repoFiles, WITAN_RUBRIC_VERSION_V17)),
|
|
5290
|
+
uncreditedLines
|
|
5288
5291
|
);
|
|
5289
5292
|
}) : /* @__PURE__ */ new Map();
|
|
5290
5293
|
const uncreditedRootWorkflows = useV27FalseAssuranceClosure && mono ? withContentReadSignals(
|
|
@@ -5299,7 +5302,8 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5299
5302
|
// The monorepo root's package.json and package-manager configuration are outside this
|
|
5300
5303
|
// scan's inventory, so none is read: the npm-family test script is not a check there
|
|
5301
5304
|
// (item E), and no npm, pnpm, yarn or bun command is a check or a setup step (third pass).
|
|
5302
|
-
UNREAD_CHECK_READING
|
|
5305
|
+
UNREAD_CHECK_READING,
|
|
5306
|
+
rootUncreditedLines
|
|
5303
5307
|
)
|
|
5304
5308
|
) : /* @__PURE__ */ new Map();
|
|
5305
5309
|
const countsAsCi = (uncredited) => (file) => !uncredited.has(file);
|
|
@@ -5317,7 +5321,7 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5317
5321
|
...checkedPublishingDetails,
|
|
5318
5322
|
...[...rootCheckedPublishingDetails].map(([file, detail]) => [`${file} at the monorepo root`, detail])
|
|
5319
5323
|
]);
|
|
5320
|
-
const
|
|
5324
|
+
const b3NoteBeforeUnreadCi = b3NoteWithCheckedPublishing(
|
|
5321
5325
|
b3Uncredited.size > 0 ? (budget) => b3UncreditedWorkflowsNote(b3Uncredited, b3PublishingDetails, budget) : void 0,
|
|
5322
5326
|
b3CheckedPublishing
|
|
5323
5327
|
);
|
|
@@ -5342,6 +5346,17 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5342
5346
|
(file) => `${file} at the monorepo root`
|
|
5343
5347
|
) : { printOnly: [] }
|
|
5344
5348
|
);
|
|
5349
|
+
const unreadCiConfigurations = useV27FalseAssuranceClosure && defaultBranchCiCount === 0 ? repoFiles.filter(isUnreadCiConfiguration).sort() : [];
|
|
5350
|
+
const publishingRuleEmptiedCount = unreadCiConfigurations.length > 0 && withContentReadSignal(
|
|
5351
|
+
"B3",
|
|
5352
|
+
"default_branch_ci_depth",
|
|
5353
|
+
() => [...uncreditedWorkflows].some(
|
|
5354
|
+
([file, reason]) => reason === "publishes" && workflowTargetsDefaultBranch(repoPath, file)
|
|
5355
|
+
) || (mono ? [...uncreditedRootWorkflows].some(
|
|
5356
|
+
([file, reason]) => reason === "publishes" && workflowTargetsDefaultBranch(mono.root, file)
|
|
5357
|
+
) : false)
|
|
5358
|
+
);
|
|
5359
|
+
const b3Note = publishingRuleEmptiedCount ? b3NoteWithUnreadCiConfigurations(b3NoteBeforeUnreadCi, unreadCiConfigurations) : b3NoteBeforeUnreadCi;
|
|
5345
5360
|
if (packageJson && scripts.has("test")) {
|
|
5346
5361
|
evidence.push(evidenceForRelative(repoPath, packageJson, "test_run", "Test script"));
|
|
5347
5362
|
}
|
|
@@ -5400,6 +5415,22 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5400
5415
|
)
|
|
5401
5416
|
);
|
|
5402
5417
|
}
|
|
5418
|
+
if (useV27FalseAssuranceClosure) {
|
|
5419
|
+
withContentReadSignals("B3", ["ci_script_depth", "default_branch_ci_depth"], () => {
|
|
5420
|
+
for (const [file, reason] of uncreditedWorkflows) {
|
|
5421
|
+
evidence.push(
|
|
5422
|
+
evidenceForRelativeAtLine(repoPath, file, "ci_run", b3UncreditedWorkflowLabel(reason, false), uncreditedLines.get(file) ?? null)
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
if (mono) {
|
|
5426
|
+
for (const [file, reason] of uncreditedRootWorkflows) {
|
|
5427
|
+
evidence.push(
|
|
5428
|
+
evidenceForRelativeAtLine(mono.root, file, "ci_run", b3UncreditedWorkflowLabel(reason, true), rootUncreditedLines.get(file) ?? null)
|
|
5429
|
+
);
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
});
|
|
5433
|
+
}
|
|
5403
5434
|
if (evidence.length === 0) return null;
|
|
5404
5435
|
const CI_BUILD_COMMAND_PATTERN = /\b(cargo\s+build|cmake|ninja|make\s+(build|all))\b/i;
|
|
5405
5436
|
const CI_COMMAND_CATEGORIES = [
|
|
@@ -5484,14 +5515,21 @@ function b3NoteWithCheckedPublishing(uncreditedNote, checked) {
|
|
|
5484
5515
|
const entries = [...checked];
|
|
5485
5516
|
const lintClause = (lint) => lint ? LINT_CONFIGURATION_CLAUSE : "";
|
|
5486
5517
|
const lintFrom = (index) => entries.slice(index).some(([, { lint }]) => lint);
|
|
5487
|
-
const
|
|
5488
|
-
|
|
5518
|
+
const gatedFrom = (index) => entries.slice(index).filter(([, { gated }]) => gated === true).length;
|
|
5519
|
+
const countSentence = (rest, more, lint, gated = 0) => {
|
|
5520
|
+
const workflows = `${rest}${more ? " more" : ""} publishing workflow${rest === 1 ? " is" : "s are"} counted as CI verification because`;
|
|
5521
|
+
const lintClause2 = lint ? rest - gated === 1 ? LINT_CONFIGURATION_CLAUSE : LINT_CONFIGURATION_SOME_CLAUSE : "";
|
|
5522
|
+
if (gated === 0) return `${workflows} a check runs before ${rest === 1 ? "it publishes" : "they publish"}${lintClause2}.`;
|
|
5523
|
+
if (gated === rest) return `${workflows} ${rest === 1 ? GATED_PUBLISHING_REASON : GATED_PUBLISHING_PLURAL_REASON}.`;
|
|
5524
|
+
return `${workflows}, in each, a check runs before it publishes${lintClause2}, or ${GATED_PUBLISHING_REASON}.`;
|
|
5525
|
+
};
|
|
5526
|
+
let note = uncreditedNote === void 0 ? "" : uncreditedNote(entries.length === 0 ? void 0 : CRITERION_NOTES_MAX_LENGTH - 1 - countSentence(entries.length, true, lintFrom(0), gatedFrom(0)).length);
|
|
5489
5527
|
const joined = (base, sentence) => base === "" ? sentence : `${base} ${sentence}`;
|
|
5490
5528
|
const fits = (text) => text.length <= CRITERION_NOTES_MAX_LENGTH;
|
|
5491
|
-
for (const [index, [name, { detail, lint }]] of entries.entries()) {
|
|
5529
|
+
for (const [index, [name, { detail, lint, gated }]] of entries.entries()) {
|
|
5492
5530
|
const rest = entries.length - index - 1;
|
|
5493
|
-
const leavesRoom = (text) => fits(text) && (rest === 0 || fits(joined(text, countSentence(rest, true, lintFrom(index + 1)))));
|
|
5494
|
-
const counted = `${name} is counted as CI verification because a check runs before it publishes${lintClause(lint)}`;
|
|
5531
|
+
const leavesRoom = (text) => fits(text) && (rest === 0 || fits(joined(text, countSentence(rest, true, lintFrom(index + 1), gatedFrom(index + 1)))));
|
|
5532
|
+
const counted = gated === true ? `${name} is counted as CI verification because ${GATED_PUBLISHING_REASON}` : `${name} is counted as CI verification because a check runs before it publishes${lintClause(lint)}`;
|
|
5495
5533
|
const full = joined(note, `${counted}; it ${detail}`);
|
|
5496
5534
|
if (leavesRoom(full)) {
|
|
5497
5535
|
note = full;
|
|
@@ -5502,11 +5540,34 @@ function b3NoteWithCheckedPublishing(uncreditedNote, checked) {
|
|
|
5502
5540
|
note = short;
|
|
5503
5541
|
continue;
|
|
5504
5542
|
}
|
|
5505
|
-
note = joined(note, countSentence(rest + 1, index > 0, lintFrom(index)));
|
|
5543
|
+
note = joined(note, countSentence(rest + 1, index > 0, lintFrom(index), gatedFrom(index)));
|
|
5506
5544
|
break;
|
|
5507
5545
|
}
|
|
5508
5546
|
return note === "" ? void 0 : note;
|
|
5509
5547
|
}
|
|
5548
|
+
function isUnreadCiConfiguration(file) {
|
|
5549
|
+
return /^(?:\.travis\.yml|\.drone\.yml|bitbucket-pipelines\.yml|appveyor\.yml|\.buildkite\/.+)$/u.test(file);
|
|
5550
|
+
}
|
|
5551
|
+
var UNREAD_CI_CONFIGURATION_LEAD = "With the publishing workflows not counted, no workflow B3 counts targets pull requests or the default branch;";
|
|
5552
|
+
function b3NoteWithUnreadCiConfigurations(note, files) {
|
|
5553
|
+
const plural2 = files.length !== 1;
|
|
5554
|
+
const sentence = (shown) => {
|
|
5555
|
+
const names = shown === 0 ? `${files.length} file${plural2 ? "s" : ""}` : shown === files.length ? shown === 1 ? files[0] : `${files.slice(0, shown - 1).join(", ")} and ${files[shown - 1]}` : `${files.slice(0, shown).join(", ")} and ${files.length - shown} more`;
|
|
5556
|
+
return `${UNREAD_CI_CONFIGURATION_LEAD} ${names} ${plural2 ? "are each" : "is"} a CI configuration Cejel does not read, matched by path alone with ${plural2 ? "their" : "its"} contents not read, so whether ${plural2 ? "they check" : "it checks"} pull requests or the default branch is not known.`;
|
|
5557
|
+
};
|
|
5558
|
+
const joined = (text) => note === void 0 || note === "" ? text : `${note} ${text}`;
|
|
5559
|
+
for (let shown = Math.min(files.length, CRITERION_NOTES_MAX_LENGTH); shown >= 0; shown -= 1) {
|
|
5560
|
+
const candidate = joined(sentence(shown));
|
|
5561
|
+
if (candidate.length <= CRITERION_NOTES_MAX_LENGTH) return candidate;
|
|
5562
|
+
}
|
|
5563
|
+
return note;
|
|
5564
|
+
}
|
|
5565
|
+
function b3UncreditedWorkflowLabel(reason, monorepoRoot) {
|
|
5566
|
+
const subject = monorepoRoot ? "Monorepo-root CI workflow not counted" : "CI workflow not counted";
|
|
5567
|
+
return reason === "publishes" ? `${subject}: publishes, releases or deploys with no pull-request trigger (cited at the publishing step, or the job calling the workflow that holds it)` : `${subject}: not shown to run a check, it ${UNCREDITED_CI_WORKFLOW_REASON_TEXT[reason]} (cited at its on: trigger)`;
|
|
5568
|
+
}
|
|
5569
|
+
var GATED_PUBLISHING_REASON = "every job that publishes, releases or deploys on every run declares an environment, a deployment gate whose protection rules Cejel cannot read offline";
|
|
5570
|
+
var GATED_PUBLISHING_PLURAL_REASON = "every job in each that publishes, releases or deploys on every run declares an environment, a deployment gate whose protection rules Cejel cannot read offline";
|
|
5510
5571
|
var LINT_CONFIGURATION_CLAUSE = " (a lint or format check, whose configuration Cejel does not read)";
|
|
5511
5572
|
var LINT_CONFIGURATION_SOME_CLAUSE = " (some on a lint or format check, whose configuration Cejel does not read)";
|
|
5512
5573
|
function b3Disclosures(unshown, ...uncredited) {
|
|
@@ -5599,12 +5660,13 @@ function isPublishingCommand(input) {
|
|
|
5599
5660
|
return false;
|
|
5600
5661
|
}
|
|
5601
5662
|
}
|
|
5602
|
-
function publishingWorkflowReasons(root, workflows, uncredited, details, checkedDetails, reading) {
|
|
5663
|
+
function publishingWorkflowReasons(root, workflows, uncredited, details, checkedDetails, reading, lines) {
|
|
5603
5664
|
const reasons = /* @__PURE__ */ new Map();
|
|
5604
5665
|
for (const file of workflows) {
|
|
5605
5666
|
const known = uncredited.get(file);
|
|
5606
5667
|
if (known) {
|
|
5607
5668
|
reasons.set(file, known);
|
|
5669
|
+
lines.set(file, workflowTriggerLine(root, file));
|
|
5608
5670
|
continue;
|
|
5609
5671
|
}
|
|
5610
5672
|
const read = publishingWorkflowDetail(root, file, reading);
|
|
@@ -5613,11 +5675,24 @@ function publishingWorkflowReasons(root, workflows, uncredited, details, checked
|
|
|
5613
5675
|
checkedDetails.set(file, { detail: read.detail, lint: read.lint });
|
|
5614
5676
|
continue;
|
|
5615
5677
|
}
|
|
5678
|
+
if (read.gated) {
|
|
5679
|
+
checkedDetails.set(file, { detail: read.detail, lint: false, gated: true });
|
|
5680
|
+
continue;
|
|
5681
|
+
}
|
|
5616
5682
|
reasons.set(file, "publishes");
|
|
5617
5683
|
details.set(file, read.detail);
|
|
5684
|
+
lines.set(file, read.line > 0 ? read.line : workflowTriggerLine(root, file));
|
|
5618
5685
|
}
|
|
5619
5686
|
return reasons;
|
|
5620
5687
|
}
|
|
5688
|
+
function workflowTriggerLine(root, file) {
|
|
5689
|
+
if (!isGitHubActionsWorkflow(file)) return null;
|
|
5690
|
+
const fullPath = join2(root, file);
|
|
5691
|
+
if (!isRegularFile(fullPath)) return null;
|
|
5692
|
+
const lines = readRepoText(fullPath, "utf8").split(/\r?\n/);
|
|
5693
|
+
const at = lines.flatMap((line, index) => WORKFLOW_ON_KEY_PATTERN.test(line) ? [index + 1] : []);
|
|
5694
|
+
return at.length === 1 ? at[0] ?? null : null;
|
|
5695
|
+
}
|
|
5621
5696
|
var RUNS_ON_KEY_PATTERN = /^[ \t]*runs-on:[ \t]*(.*)$/;
|
|
5622
5697
|
var SHELL_KEY_PATTERN = /^[ \t]*(?:-[ \t]+)?shell:[ \t]*(.*)$/;
|
|
5623
5698
|
var ONE_LABEL_LIST_PATTERN = /^\[[ \t]*([^,[\]]+?)[ \t]*\]$/;
|
|
@@ -5856,66 +5931,114 @@ function workflowJobs(contents, isGuardLine) {
|
|
|
5856
5931
|
return jobs;
|
|
5857
5932
|
}
|
|
5858
5933
|
function publishingJobsOnEveryRun(root, contents, context, depth, reading) {
|
|
5859
|
-
const none = { environments: [], checkedBeforePublish: false, notCredited: [], lintCredited: false };
|
|
5934
|
+
const none = { environments: [], checkedBeforePublish: false, notCredited: [], lintCredited: false, line: 0 };
|
|
5860
5935
|
const notCredited = [];
|
|
5861
5936
|
const credit = { lint: false };
|
|
5862
5937
|
if (YAML_LINE_BREAK_NOT_SPLIT_PATTERN.test(contents)) return none;
|
|
5938
|
+
const { isGuardLine, jobs, publishingStepsKeys, publishes, firstPublishLine } = publishingStepsOnEveryRun(contents, context);
|
|
5939
|
+
if (publishingStepsKeys.size > 0) {
|
|
5940
|
+
return {
|
|
5941
|
+
environments: publishingJobEnvironments(contents, jobs, publishingStepsKeys),
|
|
5942
|
+
checkedBeforePublish: depth === 0 && checksBeforeEveryPublish(contents, jobs, publishes, isGuardLine, reading, notCredited, credit),
|
|
5943
|
+
notCredited,
|
|
5944
|
+
lintCredited: credit.lint,
|
|
5945
|
+
line: firstPublishLine
|
|
5946
|
+
};
|
|
5947
|
+
}
|
|
5948
|
+
if (depth >= REUSABLE_WORKFLOW_MAX_DEPTH) return none;
|
|
5949
|
+
for (const job of jobs) {
|
|
5950
|
+
const { target, conditional } = job;
|
|
5951
|
+
if (target === void 0 || conditional) continue;
|
|
5952
|
+
const local = LOCAL_REUSABLE_WORKFLOW_PATTERN.exec(target);
|
|
5953
|
+
if (!local) continue;
|
|
5954
|
+
const calledPath = join2(root, local[1] ?? "");
|
|
5955
|
+
if (!isRegularFile(calledPath)) continue;
|
|
5956
|
+
const called = publishingJobsOnEveryRun(root, readRepoText(calledPath, "utf8"), context, depth + 1, reading);
|
|
5957
|
+
if (called.environments.length > 0) {
|
|
5958
|
+
return {
|
|
5959
|
+
environments: called.environments,
|
|
5960
|
+
checkedBeforePublish: depth === 0 && checksBeforeEveryPublish(contents, jobs, publishes, isGuardLine, reading, notCredited, credit),
|
|
5961
|
+
notCredited,
|
|
5962
|
+
lintCredited: credit.lint,
|
|
5963
|
+
// Item B: the calling job's `uses:` line in this file.
|
|
5964
|
+
line: jobUsesLine(contents, job)
|
|
5965
|
+
};
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
return none;
|
|
5969
|
+
}
|
|
5970
|
+
function publishingStepsOnEveryRun(contents, context) {
|
|
5863
5971
|
const isGuardLine = (line) => isStepOrJobGuard(line) && !isAlwaysTrueIfLine(line, context);
|
|
5864
5972
|
const jobs = workflowJobs(contents, isGuardLine);
|
|
5865
5973
|
const mayRun = (step) => !step.conditional && step.job !== -1;
|
|
5866
5974
|
const readsRunLines = runsOnlyOnHostedBashRunners(contents);
|
|
5867
5975
|
const publishingStepsKeys = /* @__PURE__ */ new Set();
|
|
5868
5976
|
const publishes = [];
|
|
5977
|
+
let firstPublishLine = 0;
|
|
5978
|
+
const onEveryRun = (stepsKey, line) => {
|
|
5979
|
+
publishingStepsKeys.add(stepsKey);
|
|
5980
|
+
if (line > 0 && (firstPublishLine === 0 || line < firstPublishLine)) firstPublishLine = line;
|
|
5981
|
+
};
|
|
5869
5982
|
for (const step of githubActionsUsesSteps(contents, isGuardLine)) {
|
|
5870
5983
|
const value = unquoteYamlScalar(beforeShellComment(step.lines[0]?.text ?? ""));
|
|
5871
5984
|
const at = value.indexOf("@");
|
|
5872
5985
|
if (!(at > 0 && PUBLISHING_ACTIONS.has(value.slice(0, at).toLowerCase()))) continue;
|
|
5873
5986
|
if (step.job !== -1) publishes.push({ job: step.job, line: step.lines[0]?.line ?? 0, command: 0 });
|
|
5874
|
-
if (mayRun(step))
|
|
5987
|
+
if (mayRun(step)) onEveryRun(step.job, step.lines[0]?.line ?? 0);
|
|
5875
5988
|
}
|
|
5876
5989
|
for (const step of githubActionsRunSteps(contents, isGuardLine)) {
|
|
5877
5990
|
const commands = joinShellContinuationLines(step.lines.map(({ text }) => text)).flatMap(
|
|
5878
5991
|
(text) => shellCommands(text).map((words) => ({ words }))
|
|
5879
5992
|
);
|
|
5880
5993
|
if (!commands.some(({ words }) => isPublishingCommand(words))) continue;
|
|
5994
|
+
const line = step.lines.find(({ text }) => shellCommands(text).some(isPublishingCommand)) ?? step.lines[0];
|
|
5881
5995
|
if (step.job !== -1) {
|
|
5882
|
-
const line = step.lines.find(({ text }) => shellCommands(text).some(isPublishingCommand)) ?? step.lines[0];
|
|
5883
5996
|
publishes.push({ job: step.job, line: line?.line ?? 0, command: Math.max(0, shellCommands(line?.text ?? "").findIndex(isPublishingCommand)) });
|
|
5884
5997
|
}
|
|
5885
|
-
if (readsRunLines && mayRun(step) && !stepShellIsConditional(step, commands))
|
|
5998
|
+
if (readsRunLines && mayRun(step) && !stepShellIsConditional(step, commands)) onEveryRun(step.job, line?.line ?? 0);
|
|
5886
5999
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
6000
|
+
return { isGuardLine, jobs, publishingStepsKeys, publishes, firstPublishLine };
|
|
6001
|
+
}
|
|
6002
|
+
function publishingJobEnvironments(contents, jobs, publishingStepsKeys) {
|
|
6003
|
+
const lines = contents.split(/\r?\n/);
|
|
6004
|
+
const indents = lines.map(lineIndent);
|
|
6005
|
+
const inBlockScalar = blockScalarBodyLines(lines, indents);
|
|
6006
|
+
return [...publishingStepsKeys].sort((left, right) => left - right).map((stepsKey) => {
|
|
6007
|
+
const job = jobs.find(({ start, end }) => start < stepsKey && stepsKey < end);
|
|
6008
|
+
return job ? jobEnvironment(lines, indents, inBlockScalar, job) : { kind: "unread" };
|
|
6009
|
+
});
|
|
6010
|
+
}
|
|
6011
|
+
function jobUsesLine(contents, job) {
|
|
6012
|
+
const lines = contents.split(/\r?\n/);
|
|
6013
|
+
for (let index = job.start + 1; index < job.end; index += 1) {
|
|
6014
|
+
if (JOB_USES_KEY_PATTERN.test(stripYamlComment(lines[index] ?? ""))) return index + 1;
|
|
5900
6015
|
}
|
|
5901
|
-
|
|
6016
|
+
return job.start + 1;
|
|
6017
|
+
}
|
|
6018
|
+
function everyPublishingJobEnvironment(root, contents, context, depth) {
|
|
6019
|
+
if (YAML_LINE_BREAK_NOT_SPLIT_PATTERN.test(contents)) return { environments: [], complete: false };
|
|
6020
|
+
const { jobs, publishingStepsKeys } = publishingStepsOnEveryRun(contents, context);
|
|
6021
|
+
const environments = publishingJobEnvironments(contents, jobs, publishingStepsKeys);
|
|
6022
|
+
let complete = true;
|
|
5902
6023
|
for (const { target, conditional } of jobs) {
|
|
5903
6024
|
if (target === void 0 || conditional) continue;
|
|
5904
6025
|
const local = LOCAL_REUSABLE_WORKFLOW_PATTERN.exec(target);
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
if (called.environments.length > 0) {
|
|
5910
|
-
return {
|
|
5911
|
-
environments: called.environments,
|
|
5912
|
-
checkedBeforePublish: depth === 0 && checksBeforeEveryPublish(contents, jobs, publishes, isGuardLine, reading, notCredited, credit),
|
|
5913
|
-
notCredited,
|
|
5914
|
-
lintCredited: credit.lint
|
|
5915
|
-
};
|
|
6026
|
+
const calledPath = local ? join2(root, local[1] ?? "") : "";
|
|
6027
|
+
if (!local || depth >= REUSABLE_WORKFLOW_MAX_DEPTH || !isRegularFile(calledPath)) {
|
|
6028
|
+
complete = false;
|
|
6029
|
+
continue;
|
|
5916
6030
|
}
|
|
6031
|
+
const called = everyPublishingJobEnvironment(root, readRepoText(calledPath, "utf8"), context, depth + 1);
|
|
6032
|
+
environments.push(...called.environments);
|
|
6033
|
+
complete &&= called.complete;
|
|
5917
6034
|
}
|
|
5918
|
-
return
|
|
6035
|
+
return { environments, complete };
|
|
6036
|
+
}
|
|
6037
|
+
function publishingGate(reading) {
|
|
6038
|
+
const { environments, complete } = reading;
|
|
6039
|
+
if (environments.some(({ kind }) => kind === "none")) return "ungated";
|
|
6040
|
+
const declared = (entry) => entry.kind === "named" || entry.kind === "unnamed" && entry.expression !== true;
|
|
6041
|
+
return complete && environments.length > 0 && environments.every(declared) ? "gated" : "unknown";
|
|
5919
6042
|
}
|
|
5920
6043
|
var STATUS_FUNCTION_NOT_SUCCESS_PATTERN = /\b(?:always|cancelled|failure)[ \t]*\(/i;
|
|
5921
6044
|
var SUCCESS_CALL_PATTERN = /\bsuccess[ \t]*\(/gi;
|
|
@@ -7058,7 +7181,7 @@ function publishingWorkflowDetail(root, file, reading) {
|
|
|
7058
7181
|
if (!isRegularFile(fullPath)) return void 0;
|
|
7059
7182
|
const contents = readRepoText(fullPath, "utf8");
|
|
7060
7183
|
if (YAML_LINE_BREAK_NOT_SPLIT_PATTERN.test(contents)) return void 0;
|
|
7061
|
-
if (contents.split(/\r?\n/).filter((
|
|
7184
|
+
if (contents.split(/\r?\n/).filter((line2) => WORKFLOW_ON_KEY_PATTERN.test(line2)).length !== 1) return void 0;
|
|
7062
7185
|
const triggers = githubWorkflowTriggers(contents);
|
|
7063
7186
|
if (triggers === null || triggers.size === 0 || triggers.has("workflow_call")) return void 0;
|
|
7064
7187
|
if ([...triggers].some((name) => PULL_REQUEST_TRIGGER_NAMES.has(name))) return void 0;
|
|
@@ -7066,15 +7189,31 @@ function publishingWorkflowDetail(root, file, reading) {
|
|
|
7066
7189
|
triggers,
|
|
7067
7190
|
pushBranches: triggers.has("push") ? githubPushBranchFilter(contents) : null
|
|
7068
7191
|
};
|
|
7069
|
-
const { environments, checkedBeforePublish, notCredited, lintCredited } = publishingJobsOnEveryRun(root, contents, context, 0, reading);
|
|
7192
|
+
const { environments, checkedBeforePublish, notCredited, lintCredited, line } = publishingJobsOnEveryRun(root, contents, context, 0, reading);
|
|
7070
7193
|
if (environments.length === 0) return void 0;
|
|
7071
|
-
const
|
|
7194
|
+
const triggerText = publishingTriggerText(contents, triggers);
|
|
7195
|
+
const detail = `is triggered by ${triggerText}, and ${publishingEnvironmentText(environments)}.`;
|
|
7196
|
+
if (checkedBeforePublish) return { detail, checked: true, lint: lintCredited, gated: false, line };
|
|
7197
|
+
const everyJob = everyPublishingJobEnvironment(root, contents, context, 0);
|
|
7198
|
+
const gate = publishingGate(everyJob);
|
|
7199
|
+
if (gate === "gated") {
|
|
7200
|
+
return {
|
|
7201
|
+
detail: `is triggered by ${triggerText}, and ${publishingEnvironmentText(everyJob.environments)}.`,
|
|
7202
|
+
checked: false,
|
|
7203
|
+
lint: false,
|
|
7204
|
+
gated: true,
|
|
7205
|
+
line
|
|
7206
|
+
};
|
|
7207
|
+
}
|
|
7072
7208
|
return {
|
|
7073
|
-
detail:
|
|
7074
|
-
checked:
|
|
7075
|
-
lint:
|
|
7209
|
+
detail: `${detail}${gate === "unknown" ? PUBLISHING_GATE_NOT_READ_SENTENCE : ""}${notCreditedSentence(notCredited)}`,
|
|
7210
|
+
checked: false,
|
|
7211
|
+
lint: false,
|
|
7212
|
+
gated: false,
|
|
7213
|
+
line
|
|
7076
7214
|
};
|
|
7077
7215
|
}
|
|
7216
|
+
var PUBLISHING_GATE_NOT_READ_SENTENCE = " Cejel could not read whether every job that publishes on every run declares an environment, so no deployment gate is credited.";
|
|
7078
7217
|
var NOT_CREDITED_MAX_REASONS = 3;
|
|
7079
7218
|
function notCreditedSentence(reasons) {
|
|
7080
7219
|
const distinct = [...new Set(reasons)];
|
|
@@ -7096,7 +7235,8 @@ function joinWords(items, last) {
|
|
|
7096
7235
|
function environmentNamed(written) {
|
|
7097
7236
|
const name = unquoteYamlScalar(written);
|
|
7098
7237
|
if (name === "") return { kind: "unread" };
|
|
7099
|
-
|
|
7238
|
+
if (PUBLISHING_NOTE_NAME_PATTERN.test(name)) return { kind: "named", name };
|
|
7239
|
+
return name.includes("${{") ? { kind: "unnamed", expression: true } : { kind: "unnamed" };
|
|
7100
7240
|
}
|
|
7101
7241
|
function jobEnvironment(lines, indents, inBlockScalar, job) {
|
|
7102
7242
|
const structural = (index) => {
|
|
@@ -20679,7 +20819,7 @@ var CERTIFICATE_GLOSSARY = [
|
|
|
20679
20819
|
metricNames: ["protected_path_review_gate"]
|
|
20680
20820
|
}
|
|
20681
20821
|
];
|
|
20682
|
-
var V27_PR_GATE_CI_WORKFLOW_COUNT_DEFINITION = "The number of detected CI workflow files configured for pull requests or the main or master branch, including a covering monorepo-root workflow, capped at four for scoring. Under the prospective witan-rubric-v27, a workflow file is not counted when Cejel reads every step as only checking out, printing text or doing nothing, or, for a workflow with no pull-request trigger, as only uploading an already-built artifact. Cejel makes either reading only when the runs-on value of every job names a documented GitHub-hosted Ubuntu or macOS runner label. Such a file was not shown to run a check, which is not a finding that it runs none. Under the same rubric, a workflow file with no pull-request trigger is also not counted, by this metric or by CI verification depth, when a step publishes, releases or deploys (a registry publish or upload, a release, an image push or a deploy command or action) on every run its triggers start, including through a job that calls a reusable workflow in the same repository, unless a check (a test, lint, type-check or verifying-build command whose failure stops the job) runs before every publish step, in the publishing job or in a job it needs. Cejel
|
|
20822
|
+
var V27_PR_GATE_CI_WORKFLOW_COUNT_DEFINITION = "The number of detected CI workflow files configured for pull requests or the main or master branch, including a covering monorepo-root workflow, capped at four for scoring. Under the prospective witan-rubric-v27, a workflow file is not counted when Cejel reads every step as only checking out, printing text or doing nothing, or, for a workflow with no pull-request trigger, as only uploading an already-built artifact. Cejel makes either reading only when the runs-on value of every job names a documented GitHub-hosted Ubuntu or macOS runner label. Such a file was not shown to run a check, which is not a finding that it runs none. Under the same rubric, a workflow file with no pull-request trigger is also not counted, by this metric or by CI verification depth, when a step publishes, releases or deploys (a registry publish or upload, a release, an image push or a deploy command or action) on every run its triggers start, including through a job that calls a reusable workflow in the same repository, unless a check (a test, lint, type-check or verifying-build command whose failure stops the job) runs before every publish step, in the publishing job or in a job it needs. It is counted too when every job that publishes on every run, in the workflow or in a reusable workflow in the same repository that it calls, declares a job-level environment whose name is not an expression: Cejel credits that declaration as a deployment gate, and cannot read offline the protection rules the environment sets. Each workflow file not counted is cited in the criterion's evidence at the line that decided it. Cejel reads a check before a publish only when it can read every step, the file holds no YAML escape and writes every other key and value plainly, nothing but setup runs before the check, no if condition uses a status-check function other than a plain success(), and no job needs itself or is part of a needs cycle. It also requires that the check command is not written as a path (such as ./ruff), that a test script, make target, justfile recipe, tox environment, or pdm, hatch or pipenv script the check runs reaches a test runner from a fixed list (no other task runner, such as nox, pre-commit, turbo, gradle, mvn, bazel or ctest, is read) with no project installed first by tox or hatch, that the runner or check carries only flags, and flag values, from a fixed list known to keep it failing, and no argument file, that a runner run as a Python module (python -m) has no module of its name, or of a module it or the standard library imports, in the repository where Python would import it first, that a Makefile it runs assigns no variable, that a tsc build before it writes only into a new directory outside node_modules, that a lint or format command is written in a form that fails on a finding, that every environment key it reads on the check's path (in env: mappings, in an assignment on a command's line, or written to GITHUB_ENV) is one from a fixed list of keys that do not choose what runs, that no earlier step writes to GITHUB_PATH, and that the job runs in no container and has no service container that sets env:. That is what Cejel reads, not a proof that the check can fail: it does not read a test runner's or linter's own configuration. An if condition is a gate only when it can be false under the workflow's own triggers in the repository itself (a check of the repository's own name or owner guards forks, not runs), and a continue-on-error setting keeps the file counted. Cejel reads a publishing action on any runner, and a publish command only on the same runner labels, with no shell setting other than bash.";
|
|
20683
20823
|
var V27_NON_HOLLOW_TEST_SHARE_DEFINITION = `test files that actually assert something rather than being empty or skipped. Under the prospective witan-rubric-v27, A1's note names each file a v27 rule rates hollow, and each pytest collection hook it did not read (as "hook not read", with the reason and the conftest.py's path: it could prove neither that the hook leaves no test to run nor that it puts the collected tests back, so no file was rated hollow for it), and each pytest run whose arguments or directory it could not read from the repository (as "pytest run not read", with the reason and the file: any option may be in force for it, so no file it may collect is rated hollow). A collection hook that removes or skips every test is applied to every test file a pytest run that loads it collects, inside its directory and outside it, since pytest calls it with every collected test. A path that holds a comma or semicolon followed by a space, a space followed by an opening parenthesis, a backtick or a double quote is written in double quotes, with each backslash and double quote in it preceded by a backslash; any other path is written as it is. The evidence cites the first eight files rated hollow, and the first eight hooks, pytest settings and runs the note names where a rule was not applied (each at the line that holds it), and the note says how many it names beyond those carry no evidence pointer.`;
|
|
20684
20824
|
function glossaryEntryForRubric(entry, rubricVersion) {
|
|
20685
20825
|
if (entry.key !== "pr-gate-ci-workflow-count" && entry.key !== "non-hollow" || rubricVersion === void 0) return entry;
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
|
|
|
3
3
|
const require = __cejelCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
serveCejelMcpOverStdio
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QIBLNTZB.js";
|
|
7
7
|
import {
|
|
8
8
|
CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
|
|
9
9
|
CERTIFICATE_GLOSSARY,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
verifyRecordedInputDigests,
|
|
46
46
|
verifyWitanAttestationBinding,
|
|
47
47
|
witanHistoryAbstentionInstruction
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-73XAL26O.js";
|
|
49
49
|
import "./chunk-2UMHJ23V.js";
|
|
50
50
|
import {
|
|
51
51
|
external_exports
|
package/dist/mcp/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
|
|
|
3
3
|
const require = __cejelCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
serveCejelMcpOverStdio
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-QIBLNTZB.js";
|
|
7
|
+
import "../chunk-73XAL26O.js";
|
|
8
8
|
import "../chunk-2UMHJ23V.js";
|
|
9
9
|
import "../chunk-AMVP7L26.js";
|
|
10
10
|
import {
|