codictator 0.1.2 → 0.2.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/dist/builtin-packs/core/fixtures/conventional-commit/passing/basic/case.json +1 -1
- package/dist/builtin-packs/core/fixtures/conventional-commit/violating/basic/case.json +1 -1
- package/dist/builtin-packs/typescript/fixtures/lint/passing/exported/case.json +8 -0
- package/dist/builtin-packs/typescript/pack.json +1 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +3 -1
- package/dist/cli/gate-scope.d.ts +1 -0
- package/dist/cli/gate-scope.js +11 -0
- package/dist/cli/git-hook.js +13 -8
- package/dist/cli/guardrail.d.ts +2 -0
- package/dist/cli/guardrail.js +11 -13
- package/dist/cli/init-plan.d.ts +7 -0
- package/dist/cli/init-plan.js +21 -2
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +8 -17
- package/dist/cli/plan-launcher.d.ts +1 -0
- package/dist/cli/plan-launcher.js +53 -3
- package/dist/cli/serve-run.js +1 -0
- package/dist/cli/setup-gitignore.d.ts +7 -0
- package/dist/cli/setup-gitignore.js +41 -0
- package/dist/cli/setup-handoff.d.ts +7 -0
- package/dist/cli/setup-handoff.js +44 -0
- package/dist/cli/setup-install.d.ts +11 -0
- package/dist/cli/setup-install.js +84 -0
- package/dist/cli/setup.d.ts +9 -0
- package/dist/cli/setup.js +143 -0
- package/dist/cli/task-advance.js +16 -16
- package/dist/features/adapters/claude-shaped.d.ts +1 -0
- package/dist/features/adapters/claude-shaped.js +29 -31
- package/dist/features/adapters/copilot.js +22 -29
- package/dist/features/audit/append.js +9 -4
- package/dist/features/audit/surfaces.js +15 -8
- package/dist/features/ci/protection.js +37 -13
- package/dist/features/deps/npm.js +39 -24
- package/dist/features/deps/registry.js +45 -34
- package/dist/features/deps/stable.js +52 -27
- package/dist/features/deps/upgrade.d.ts +1 -1
- package/dist/features/deps/upgrade.js +29 -19
- package/dist/features/engine/evaluate.js +6 -1
- package/dist/features/packs/enforcers/ci-guards.js +4 -1
- package/dist/features/packs/enforcers/code.js +2 -1
- package/dist/features/packs/enforcers/engine-version-guards.d.ts +6 -0
- package/dist/features/packs/enforcers/engine-version-guards.js +42 -0
- package/dist/features/packs/enforcers/external-bind.js +62 -28
- package/dist/features/packs/enforcers/external-sarif.js +30 -24
- package/dist/features/packs/enforcers/gate-guards.d.ts +0 -1
- package/dist/features/packs/enforcers/gate-guards.js +83 -136
- package/dist/features/packs/enforcers/health-measures.d.ts +2 -0
- package/dist/features/packs/enforcers/health-measures.js +10 -1
- package/dist/features/packs/enforcers/health-refs.d.ts +1 -2
- package/dist/features/packs/enforcers/health-refs.js +71 -56
- package/dist/features/packs/enforcers/secret-scan.d.ts +4 -20
- package/dist/features/packs/enforcers/secret-scan.js +61 -67
- package/dist/features/packs/enforcers/ts-checks.js +104 -58
- package/dist/features/packs/enforcers/ts-security-patterns.js +12 -8
- package/dist/features/packs/manifest-decl.d.ts +13 -0
- package/dist/features/packs/manifest-decl.js +121 -0
- package/dist/features/packs/manifest.d.ts +2 -12
- package/dist/features/packs/manifest.js +5 -90
- package/dist/features/packs/selftest.js +22 -5
- package/dist/features/remediation/adoption.js +40 -29
- package/dist/features/testing/scenarios.js +49 -27
- package/dist/features/testing/tap-parser.js +38 -26
- package/dist/features/workflow/phases.js +45 -42
- package/dist/features/workflow/review.js +17 -10
- package/dist/kernel/git.js +4 -1
- package/dist/kernel/pattern.js +120 -86
- package/dist/kernel/secret-paths.js +34 -20
- package/dist/kernel/shell-cmd.js +23 -6
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -14,24 +14,37 @@ export const DEFAULT_DEP_LAG_PARAMS = {
|
|
|
14
14
|
// SPEC §6.1: an exact pin is a full x.y.z with an optional -pre suffix;
|
|
15
15
|
// compare follows npm version order (numeric parts first, release beats
|
|
16
16
|
// prerelease, prerelease identifiers numeric then lexical).
|
|
17
|
+
// The pattern lives at module scope so its quantifiers never count as
|
|
18
|
+
// branches of the function that applies it.
|
|
19
|
+
const NPM_VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
20
|
+
function versionPart(match, index) {
|
|
21
|
+
return Number(match[index] ?? "");
|
|
22
|
+
}
|
|
23
|
+
function narrowVersionParts(match) {
|
|
24
|
+
const major = versionPart(match, 1);
|
|
25
|
+
const minor = versionPart(match, 2);
|
|
26
|
+
const patch = versionPart(match, 3);
|
|
27
|
+
if (!Number.isSafeInteger(major) ||
|
|
28
|
+
!Number.isSafeInteger(minor) ||
|
|
29
|
+
!Number.isSafeInteger(patch)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return { major, minor, patch };
|
|
33
|
+
}
|
|
17
34
|
export function parseNpmVersion(value) {
|
|
18
|
-
const match =
|
|
35
|
+
const match = NPM_VERSION_PATTERN.exec(value);
|
|
19
36
|
if (match === null) {
|
|
20
37
|
return undefined;
|
|
21
38
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const patch = Number(match[3] ?? "");
|
|
25
|
-
if (!Number.isSafeInteger(major) ||
|
|
26
|
-
!Number.isSafeInteger(minor) ||
|
|
27
|
-
!Number.isSafeInteger(patch)) {
|
|
39
|
+
const parts = narrowVersionParts(match);
|
|
40
|
+
if (parts === undefined) {
|
|
28
41
|
return undefined;
|
|
29
42
|
}
|
|
30
43
|
const pre = match[4];
|
|
31
44
|
if (pre === undefined || pre === "") {
|
|
32
|
-
return {
|
|
45
|
+
return { ...parts, prerelease: undefined };
|
|
33
46
|
}
|
|
34
|
-
return {
|
|
47
|
+
return { ...parts, prerelease: pre.split(".") };
|
|
35
48
|
}
|
|
36
49
|
export function isNpmPrerelease(value) {
|
|
37
50
|
const parsed = parseNpmVersion(value);
|
|
@@ -65,12 +78,13 @@ function compareIdentifiers(left, right) {
|
|
|
65
78
|
}
|
|
66
79
|
return 0;
|
|
67
80
|
}
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
function compareVersionCore(left, right) {
|
|
82
|
+
const pairs = [
|
|
70
83
|
[left.major, right.major],
|
|
71
84
|
[left.minor, right.minor],
|
|
72
85
|
[left.patch, right.patch],
|
|
73
|
-
]
|
|
86
|
+
];
|
|
87
|
+
for (const pair of pairs) {
|
|
74
88
|
if (pair[0] < pair[1]) {
|
|
75
89
|
return -1;
|
|
76
90
|
}
|
|
@@ -78,29 +92,40 @@ export function compareNpmVersions(left, right) {
|
|
|
78
92
|
return 1;
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
function prereleaseOrderAt(left, right, index) {
|
|
98
|
+
const leftId = left[index];
|
|
99
|
+
const rightId = right[index];
|
|
100
|
+
if (leftId === undefined) {
|
|
101
|
+
return -1;
|
|
83
102
|
}
|
|
84
|
-
if (
|
|
103
|
+
if (rightId === undefined) {
|
|
85
104
|
return 1;
|
|
86
105
|
}
|
|
87
|
-
|
|
106
|
+
const order = compareIdentifiers(leftId, rightId);
|
|
107
|
+
return order === 0 ? undefined : order;
|
|
108
|
+
}
|
|
109
|
+
function comparePrerelease(left, right) {
|
|
110
|
+
if (left === undefined) {
|
|
111
|
+
return right === undefined ? 0 : 1;
|
|
112
|
+
}
|
|
113
|
+
if (right === undefined) {
|
|
88
114
|
return -1;
|
|
89
115
|
}
|
|
90
|
-
const length = Math.max(left.
|
|
116
|
+
const length = Math.max(left.length, right.length);
|
|
91
117
|
for (let index = 0; index < length; index += 1) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
if (leftId === undefined) {
|
|
95
|
-
return -1;
|
|
96
|
-
}
|
|
97
|
-
if (rightId === undefined) {
|
|
98
|
-
return 1;
|
|
99
|
-
}
|
|
100
|
-
const order = compareIdentifiers(leftId, rightId);
|
|
101
|
-
if (order !== 0) {
|
|
118
|
+
const order = prereleaseOrderAt(left, right, index);
|
|
119
|
+
if (order !== undefined) {
|
|
102
120
|
return order;
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
return 0;
|
|
106
124
|
}
|
|
125
|
+
export function compareNpmVersions(left, right) {
|
|
126
|
+
const core = compareVersionCore(left, right);
|
|
127
|
+
if (core !== 0) {
|
|
128
|
+
return core;
|
|
129
|
+
}
|
|
130
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
131
|
+
}
|
|
@@ -30,6 +30,6 @@ export interface UpgradeHost {
|
|
|
30
30
|
readonly push: (branch: string) => Promise<Result<void>>;
|
|
31
31
|
}
|
|
32
32
|
export declare function upgradeCommitSubject(name: string, from: string, to: string): string;
|
|
33
|
-
export declare function upgradeHistoryEntry(date: string,
|
|
33
|
+
export declare function upgradeHistoryEntry(date: string, commitSubject: string, name: string, from: string, to: string, task: string, advisoryIds: ReadonlyArray<string>): string;
|
|
34
34
|
export declare function planUpgradePr(input: UpgradePrInput): Result<UpgradePrPlan>;
|
|
35
35
|
export declare function runUpgradePr(plan: UpgradePrPlan, host: UpgradeHost): Promise<Result<UpgradePrPlan>>;
|
|
@@ -12,10 +12,10 @@ export function upgradeCommitSubject(name, from, to) {
|
|
|
12
12
|
}
|
|
13
13
|
// SPEC §7.7: a deps entry carries Kind, Task, Why and one Deps line per
|
|
14
14
|
// change in the added, upgraded or removed form.
|
|
15
|
-
export function upgradeHistoryEntry(date,
|
|
15
|
+
export function upgradeHistoryEntry(date, commitSubject, name, from, to, task, advisoryIds) {
|
|
16
16
|
const rendered = advisoryIds.length === 0 ? "none" : [...advisoryIds].sort().join(", ");
|
|
17
17
|
return [
|
|
18
|
-
`## ${date} · ${
|
|
18
|
+
`## ${date} · ${commitSubject}`,
|
|
19
19
|
`- **Kind:** deps`,
|
|
20
20
|
`- **Task:** ${task}`,
|
|
21
21
|
`- **Why:** L5 sweep upgrades ${name} to latest stable ${to} (SPEC §4.8.7).`,
|
|
@@ -54,25 +54,19 @@ function appendHistoryEntry(historyText, date, entry) {
|
|
|
54
54
|
}
|
|
55
55
|
return `${title}\n\n${entry}\n\n${rest.endsWith("\n") ? rest : `${rest}\n`}`;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export function planUpgradePr(input) {
|
|
61
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(input.date)) {
|
|
62
|
-
return {
|
|
63
|
-
ok: false,
|
|
64
|
-
error: makeError("CDX-E903", `upgrade date ${input.date} is not a UTC YYYY-MM-DD date`, "Pass the commit date in UTC as YYYY-MM-DD."),
|
|
65
|
-
};
|
|
57
|
+
function checkUpgradeDate(date) {
|
|
58
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
59
|
+
return { ok: true, value: date };
|
|
66
60
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: makeError("CDX-E903", `upgrade date ${date} is not a UTC YYYY-MM-DD date`, "Pass the commit date in UTC as YYYY-MM-DD."),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function upgradePlanFiles(input, manifestText, entry) {
|
|
73
67
|
const historyPath = input.historyPath ?? `docs/HISTORY/${input.date.slice(0, 10)}.md`;
|
|
74
68
|
const files = [
|
|
75
|
-
{ path: input.manifestPath, text:
|
|
69
|
+
{ path: input.manifestPath, text: manifestText },
|
|
76
70
|
{
|
|
77
71
|
path: historyPath,
|
|
78
72
|
text: appendHistoryEntry(input.historyText, input.date, entry),
|
|
@@ -81,12 +75,28 @@ export function planUpgradePr(input) {
|
|
|
81
75
|
if (input.lockfilePath !== undefined && input.lockfileText !== undefined) {
|
|
82
76
|
files.push({ path: input.lockfilePath, text: input.lockfileText });
|
|
83
77
|
}
|
|
78
|
+
return files;
|
|
79
|
+
}
|
|
80
|
+
// SPEC §4.8.7: the PR holds the manifest edit, the regenerated lockfile and
|
|
81
|
+
// its history entry. The lockfile bytes come from the caller (regenerated by
|
|
82
|
+
// the ecosystem tool); without them the plan holds manifest plus history.
|
|
83
|
+
export function planUpgradePr(input) {
|
|
84
|
+
const dated = checkUpgradeDate(input.date);
|
|
85
|
+
if (!dated.ok) {
|
|
86
|
+
return dated;
|
|
87
|
+
}
|
|
88
|
+
const bumped = bumpManifestPin(input.manifestText, input.name, input.from, input.to);
|
|
89
|
+
if (!bumped.ok) {
|
|
90
|
+
return bumped;
|
|
91
|
+
}
|
|
92
|
+
const subject = upgradeCommitSubject(input.name, input.from, input.to);
|
|
93
|
+
const entry = upgradeHistoryEntry(input.date, subject, input.name, input.from, input.to, input.task ?? "none", input.advisoryIds ?? []);
|
|
84
94
|
return {
|
|
85
95
|
ok: true,
|
|
86
96
|
value: {
|
|
87
97
|
branch: upgradeBranchName(input.ecosystem, input.name, input.to),
|
|
88
98
|
subject,
|
|
89
|
-
files,
|
|
99
|
+
files: upgradePlanFiles(input, bumped.value, entry),
|
|
90
100
|
},
|
|
91
101
|
};
|
|
92
102
|
}
|
|
@@ -93,7 +93,12 @@ function ruleIdsOf(findings) {
|
|
|
93
93
|
}
|
|
94
94
|
async function collectFindings(ordered, ctx, store) {
|
|
95
95
|
const runnable = ordered.filter((enforcer) => enforcer.kinds.includes(ctx.trigger));
|
|
96
|
-
|
|
96
|
+
// SPEC §4.3.3 step 2: bounded concurrency — one guard at a time keeps every
|
|
97
|
+
// guard's event-loop share (no E902 herd); sortFindings keeps the verdict byte-identical (§4.3.6).
|
|
98
|
+
const ran = [];
|
|
99
|
+
for (const enforcer of runnable) {
|
|
100
|
+
ran.push(await runEnforcer(enforcer, ctx));
|
|
101
|
+
}
|
|
97
102
|
return applyWaivers(sortFindings(ran.flat()), store, ctx.now);
|
|
98
103
|
}
|
|
99
104
|
function decideDecision(kept, trigger) {
|
|
@@ -110,7 +110,10 @@ export function resolveConventionalCommit(decl, rule, ports) {
|
|
|
110
110
|
const findings = [];
|
|
111
111
|
for (const file of selectFiles(ctx.scope.files, binding.scopeGlobs)) {
|
|
112
112
|
const text = await loadScopeText(ports, binding, file, findings);
|
|
113
|
-
|
|
113
|
+
// Commit subjects arrive as marker reports (SPEC §4.4.1
|
|
114
|
+
// sample.txt fixtures); source files carry `subject:` lines
|
|
115
|
+
// of their own (parameters, YAML keys), which are not commits.
|
|
116
|
+
if (text === undefined || !isMarkerFile(file)) {
|
|
114
117
|
continue;
|
|
115
118
|
}
|
|
116
119
|
const subject = parseMarkers(text).get("subject") ?? "";
|
|
@@ -6,7 +6,8 @@ import { resolveDepExactPin, resolveDepLatestStable, resolveDepLockfile, resolve
|
|
|
6
6
|
import { resolveDepLicense, resolveDepNoDeprecated, resolveDepNoVulnerable, resolveDepOnePerCapability, } from "./deps-policy-guards.js";
|
|
7
7
|
import { resolveDocNoCorpusCounts, resolveDocOneLevel, resolveDocPlacement, resolveDocRouterLeaf, resolveDocSingleOwner, } from "./docs-guards.js";
|
|
8
8
|
import { resolveDocBreadth, resolveDocDensity, resolveDocLinksValid, resolveDocNoInlineChangelog, } from "./docs-guards-measures.js";
|
|
9
|
-
import {
|
|
9
|
+
import { resolveEngineVersionMatch } from "./engine-version-guards.js";
|
|
10
|
+
import { resolveAgentAssetsPresent, resolveCustomPackSelftest, resolveGuardrailManifest, resolveHooksActive, resolvePerfBudgets, resolveWaiversValid, } from "./gate-guards.js";
|
|
10
11
|
import { resolveNoSpeculativeAbstraction } from "./health-abstraction.js";
|
|
11
12
|
import { resolveDuplication } from "./health-duplication.js";
|
|
12
13
|
import { resolveDeadCode } from "./health-refs.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Ports } from "../../../kernel/index.ts";
|
|
2
|
+
import { type Result } from "../../../kernel/index.ts";
|
|
3
|
+
import type { Enforcer } from "../../engine/index.ts";
|
|
4
|
+
import type { PackRule } from "../index.ts";
|
|
5
|
+
import type { EnforcerDecl } from "../manifest.ts";
|
|
6
|
+
export declare function resolveEngineVersionMatch(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseJson } from "../../../kernel/index.js";
|
|
2
|
+
import { findingFor, readErrorFinding, selectFiles, } from "./binding.js";
|
|
3
|
+
import { checkEnforcer, loadScopeText } from "./health-measures.js";
|
|
4
|
+
async function checkEngineVersionFile(ports, binding, file, findings) {
|
|
5
|
+
const text = await loadScopeText(ports, binding, file, findings);
|
|
6
|
+
if (text === undefined) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const parsed = parseJson(text);
|
|
10
|
+
if (!parsed.ok) {
|
|
11
|
+
findings.push(readErrorFinding(binding, file, parsed.error, "parse"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const record = (typeof parsed.value === "object" && parsed.value !== null
|
|
15
|
+
? parsed.value
|
|
16
|
+
: {});
|
|
17
|
+
if (typeof record.engine !== "string" ||
|
|
18
|
+
typeof record.pack !== "string" ||
|
|
19
|
+
record.engine === record.pack) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
findings.push(findingFor(binding, {
|
|
23
|
+
path: file,
|
|
24
|
+
message: `pack version ${record.pack} does not match the engine ${record.engine}`,
|
|
25
|
+
vars: { path: file, value: record.engine },
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
async function checkEngineVersionFiles(ports, binding, ctx) {
|
|
29
|
+
const findings = [];
|
|
30
|
+
for (const file of selectFiles(ctx.scope.files, binding.scopeGlobs)) {
|
|
31
|
+
if (file !== "engine.json" && !file.endsWith("/engine.json")) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
await checkEngineVersionFile(ports, binding, file, findings);
|
|
35
|
+
}
|
|
36
|
+
return findings;
|
|
37
|
+
}
|
|
38
|
+
// SPEC §4.14.2 core/engine-version-match: the declared version must equal
|
|
39
|
+
// the installed engine; a pack ahead of the engine flags.
|
|
40
|
+
export function resolveEngineVersionMatch(decl, rule, ports) {
|
|
41
|
+
return checkEnforcer(decl, rule, (binding, ctx) => checkEngineVersionFiles(ports, binding, ctx));
|
|
42
|
+
}
|
|
@@ -15,17 +15,20 @@ function stringList(value) {
|
|
|
15
15
|
}
|
|
16
16
|
return undefined;
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
function narrowTimeoutMs(timeout) {
|
|
19
|
+
return typeof timeout === "number" && Number.isInteger(timeout) && timeout > 0
|
|
20
|
+
? timeout
|
|
21
|
+
: DEFAULT_TIMEOUT_MS;
|
|
22
|
+
}
|
|
23
|
+
function narrowToolIdentity(entry) {
|
|
24
24
|
const id = field(entry, "id");
|
|
25
25
|
const command = stringList(field(entry, "command")) ?? [];
|
|
26
26
|
if (typeof id !== "string" || id.length === 0 || command.length === 0) {
|
|
27
27
|
return toolError("CDX-E203", "a config.tools entry needs a non-empty id and command argv");
|
|
28
28
|
}
|
|
29
|
+
return ok({ id, command });
|
|
30
|
+
}
|
|
31
|
+
function narrowToolVersion(entry, id) {
|
|
29
32
|
const version = field(entry, "version");
|
|
30
33
|
if (!isRecord(version)) {
|
|
31
34
|
return toolError("CDX-E206", `tool "${id}" has no exact version pin`);
|
|
@@ -41,47 +44,78 @@ export function narrowToolDecl(entry) {
|
|
|
41
44
|
pattern.length === 0) {
|
|
42
45
|
return toolError("CDX-E203", `tool "${id}" needs a probe argv and a pattern`);
|
|
43
46
|
}
|
|
47
|
+
return ok({ pin, probe, pattern });
|
|
48
|
+
}
|
|
49
|
+
// SPEC §7.2: a tools entry needs an id, an argv command and an exact pin
|
|
50
|
+
// with its probe. CDX-E206 when the pin is missing (§8).
|
|
51
|
+
export function narrowToolDecl(entry) {
|
|
52
|
+
if (!isRecord(entry)) {
|
|
53
|
+
return toolError("CDX-E203", "a config.tools entry must be an object");
|
|
54
|
+
}
|
|
55
|
+
const identity = narrowToolIdentity(entry);
|
|
56
|
+
if (!identity.ok) {
|
|
57
|
+
return identity;
|
|
58
|
+
}
|
|
59
|
+
const version = narrowToolVersion(entry, identity.value.id);
|
|
60
|
+
if (!version.ok) {
|
|
61
|
+
return version;
|
|
62
|
+
}
|
|
44
63
|
const role = field(entry, "role");
|
|
45
64
|
if (typeof role === "string" && role !== "enforcer") {
|
|
46
|
-
return toolError("CDX-E203", `tool "${id}" is a helper and cannot back an enforcer`);
|
|
65
|
+
return toolError("CDX-E203", `tool "${identity.value.id}" is a helper and cannot back an enforcer`);
|
|
47
66
|
}
|
|
48
67
|
const timeout = field(entry, "timeoutMs");
|
|
49
68
|
const envPass = stringList(field(entry, "envPass")) ?? [];
|
|
50
69
|
return ok({
|
|
51
|
-
id,
|
|
52
|
-
command,
|
|
53
|
-
version:
|
|
70
|
+
id: identity.value.id,
|
|
71
|
+
command: identity.value.command,
|
|
72
|
+
version: version.value,
|
|
54
73
|
role: typeof role === "string" ? role : undefined,
|
|
55
|
-
timeoutMs:
|
|
56
|
-
? timeout
|
|
57
|
-
: DEFAULT_TIMEOUT_MS,
|
|
74
|
+
timeoutMs: narrowTimeoutMs(timeout),
|
|
58
75
|
envPass,
|
|
59
76
|
});
|
|
60
77
|
}
|
|
78
|
+
function narrowOutputFormat(value, withId) {
|
|
79
|
+
if (value === "sarif" ||
|
|
80
|
+
value === "json" ||
|
|
81
|
+
value === "regex" ||
|
|
82
|
+
value === "exit-code") {
|
|
83
|
+
return ok(value);
|
|
84
|
+
}
|
|
85
|
+
return withId("needs an output format of sarif, json, regex or exit-code");
|
|
86
|
+
}
|
|
87
|
+
function narrowOutputSource(value, withId) {
|
|
88
|
+
const source = value ?? "stdout";
|
|
89
|
+
if (source === "stdout" || source === "{out}") {
|
|
90
|
+
return ok(source);
|
|
91
|
+
}
|
|
92
|
+
return withId("needs an output source of stdout or {out}");
|
|
93
|
+
}
|
|
61
94
|
function narrowOutput(declId, value) {
|
|
62
95
|
if (!isRecord(value)) {
|
|
63
96
|
return toolError(declId, "needs an output binding");
|
|
64
97
|
}
|
|
65
98
|
const withId = (detail) => toolError("CDX-E203", `pack enforcer "${declId}" ${detail}`);
|
|
66
|
-
const format = field(value, "format");
|
|
67
|
-
if (format
|
|
68
|
-
format
|
|
69
|
-
format !== "regex" &&
|
|
70
|
-
format !== "exit-code") {
|
|
71
|
-
return withId("needs an output format of sarif, json, regex or exit-code");
|
|
99
|
+
const format = narrowOutputFormat(field(value, "format"), withId);
|
|
100
|
+
if (!format.ok) {
|
|
101
|
+
return format;
|
|
72
102
|
}
|
|
73
|
-
const source = field(value, "source")
|
|
74
|
-
if (source
|
|
75
|
-
return
|
|
103
|
+
const source = narrowOutputSource(field(value, "source"), withId);
|
|
104
|
+
if (!source.ok) {
|
|
105
|
+
return source;
|
|
76
106
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return narrowJsonSelect(withId, format, output, field(value, "select"));
|
|
107
|
+
if (format.value === "json") {
|
|
108
|
+
return narrowJsonSelect(withId, format.value, source.value, field(value, "select"));
|
|
80
109
|
}
|
|
81
|
-
if (format === "regex") {
|
|
82
|
-
return narrowRegexPattern(withId, format,
|
|
110
|
+
if (format.value === "regex") {
|
|
111
|
+
return narrowRegexPattern(withId, format.value, source.value, field(value, "pattern"));
|
|
83
112
|
}
|
|
84
|
-
return ok({
|
|
113
|
+
return ok({
|
|
114
|
+
format: format.value,
|
|
115
|
+
source: source.value,
|
|
116
|
+
select: undefined,
|
|
117
|
+
pattern: undefined,
|
|
118
|
+
});
|
|
85
119
|
}
|
|
86
120
|
function narrowJsonSelect(withId, format, output, select) {
|
|
87
121
|
if (!isRecord(select)) {
|
|
@@ -59,6 +59,35 @@ function sarifLocation(result) {
|
|
|
59
59
|
}
|
|
60
60
|
return { path: uri, line };
|
|
61
61
|
}
|
|
62
|
+
function collectSarifEntry(entry, run, findings, escapes) {
|
|
63
|
+
if (!isRecord(entry)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const toolRule = sarifRuleId(entry, run);
|
|
67
|
+
if (toolRule === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const location = sarifLocation(entry);
|
|
71
|
+
const detail = field(entry, "message");
|
|
72
|
+
pushEntry(findings, escapes, {
|
|
73
|
+
toolRule,
|
|
74
|
+
path: location.path,
|
|
75
|
+
line: location.line,
|
|
76
|
+
message: isRecord(detail) ? field(detail, "text") : undefined,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function collectSarifRun(run, findings, escapes) {
|
|
80
|
+
if (!isRecord(run)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const results = field(run, "results");
|
|
84
|
+
if (!Array.isArray(results)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const entry of results) {
|
|
88
|
+
collectSarifEntry(entry, run, findings, escapes);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
62
91
|
export function parseSarif(text) {
|
|
63
92
|
const parsed = parseJson(text);
|
|
64
93
|
if (!parsed.ok) {
|
|
@@ -74,30 +103,7 @@ export function parseSarif(text) {
|
|
|
74
103
|
const findings = [];
|
|
75
104
|
const escapes = { count: 0 };
|
|
76
105
|
for (const run of runs) {
|
|
77
|
-
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const results = field(run, "results");
|
|
81
|
-
if (!Array.isArray(results)) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
for (const entry of results) {
|
|
85
|
-
if (!isRecord(entry)) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const toolRule = sarifRuleId(entry, run);
|
|
89
|
-
if (toolRule === undefined) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const location = sarifLocation(entry);
|
|
93
|
-
const detail = field(entry, "message");
|
|
94
|
-
pushEntry(findings, escapes, {
|
|
95
|
-
toolRule,
|
|
96
|
-
path: location.path,
|
|
97
|
-
line: location.line,
|
|
98
|
-
message: isRecord(detail) ? field(detail, "text") : undefined,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
106
|
+
collectSarifRun(run, findings, escapes);
|
|
101
107
|
}
|
|
102
108
|
return ok({ findings, escapes: escapes.count });
|
|
103
109
|
}
|
|
@@ -7,6 +7,5 @@ export declare function resolveAgentAssetsPresent(decl: EnforcerDecl, rule: Pack
|
|
|
7
7
|
export declare function resolvePerfBudgets(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|
|
8
8
|
export declare function resolveGuardrailManifest(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|
|
9
9
|
export declare function resolveHooksActive(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|
|
10
|
-
export declare function resolveEngineVersionMatch(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|
|
11
10
|
export declare function resolveWaiversValid(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|
|
12
11
|
export declare function resolveCustomPackSelftest(decl: EnforcerDecl, rule: PackRule, ports: Ports): Result<Enforcer>;
|