coderifts 4.7.0 → 4.9.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 +64 -0
- package/README.md +46 -10
- package/bin/coderifts.js +1 -1
- package/dist/cli.js +620 -97
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3028,7 +3028,7 @@ var require_package = __commonJS({
|
|
|
3028
3028
|
"package.json"(exports2, module2) {
|
|
3029
3029
|
module2.exports = {
|
|
3030
3030
|
name: "coderifts",
|
|
3031
|
-
version: "4.
|
|
3031
|
+
version: "4.9.0",
|
|
3032
3032
|
description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
3033
3033
|
author: "CodeRifts <hello@coderifts.com>",
|
|
3034
3034
|
license: "MIT",
|
|
@@ -74363,7 +74363,7 @@ var require_claude_hook = __commonJS({
|
|
|
74363
74363
|
"Parse-gap DEFAULT (exit 2): unparseable stdin / missing file_path \u2014 refusing (fail-closed);",
|
|
74364
74364
|
" set CODERIFTS_ADVISORY=1 to soften. JSONL hook_blocked cause stdin_unparseable|missing_file_path.",
|
|
74365
74365
|
"",
|
|
74366
|
-
"Still exit 0 (nothing to govern): non-spec path, identical content.",
|
|
74366
|
+
"Still exit 0 (nothing to govern): non-spec path, identical content, non-governed tool.",
|
|
74367
74367
|
"",
|
|
74368
74368
|
"CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
|
|
74369
74369
|
" (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
|
|
@@ -74504,6 +74504,42 @@ var require_claude_hook = __commonJS({
|
|
|
74504
74504
|
if (fromGit) return fromGit;
|
|
74505
74505
|
return DEFAULT_SPEC_PATH;
|
|
74506
74506
|
}
|
|
74507
|
+
var GOVERNED_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "multiedit", "multi_edit"]);
|
|
74508
|
+
function isGovernedTool(name) {
|
|
74509
|
+
return GOVERNED_TOOLS.has(String(name || "").toLowerCase());
|
|
74510
|
+
}
|
|
74511
|
+
function pickToolField(obj, ...keys) {
|
|
74512
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return void 0;
|
|
74513
|
+
for (const k of keys) {
|
|
74514
|
+
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
74515
|
+
const v = obj[k];
|
|
74516
|
+
if (v === void 0 || v === null) continue;
|
|
74517
|
+
return v;
|
|
74518
|
+
}
|
|
74519
|
+
return void 0;
|
|
74520
|
+
}
|
|
74521
|
+
function filePathFromInput(toolInput) {
|
|
74522
|
+
const v = pickToolField(toolInput, "file_path", "filePath", "path", "target_file", "targetFile");
|
|
74523
|
+
return typeof v === "string" && v !== "" ? v : void 0;
|
|
74524
|
+
}
|
|
74525
|
+
function contentFromInput(toolInput) {
|
|
74526
|
+
const v = pickToolField(toolInput, "content", "contents");
|
|
74527
|
+
return typeof v === "string" ? v : void 0;
|
|
74528
|
+
}
|
|
74529
|
+
function coerceToolInput(value) {
|
|
74530
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
74531
|
+
if (typeof value === "string") {
|
|
74532
|
+
const t = value.trim();
|
|
74533
|
+
if (!t) return null;
|
|
74534
|
+
try {
|
|
74535
|
+
const parsed = JSON.parse(t);
|
|
74536
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
74537
|
+
} catch {
|
|
74538
|
+
return null;
|
|
74539
|
+
}
|
|
74540
|
+
}
|
|
74541
|
+
return null;
|
|
74542
|
+
}
|
|
74507
74543
|
function parseStdinJson(raw) {
|
|
74508
74544
|
if (raw == null || String(raw).trim() === "") {
|
|
74509
74545
|
return { ok: false, reason: "empty stdin" };
|
|
@@ -74517,9 +74553,9 @@ var require_claude_hook = __commonJS({
|
|
|
74517
74553
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
74518
74554
|
return { ok: false, reason: "stdin JSON is not an object" };
|
|
74519
74555
|
}
|
|
74520
|
-
const toolName = obj
|
|
74521
|
-
let toolInput = obj
|
|
74522
|
-
if (toolInput == null && obj
|
|
74556
|
+
const toolName = pickToolField(obj, "tool_name", "toolName", "name");
|
|
74557
|
+
let toolInput = coerceToolInput(pickToolField(obj, "tool_input", "toolInput", "input"));
|
|
74558
|
+
if (toolInput == null && filePathFromInput(obj)) toolInput = obj;
|
|
74523
74559
|
if (!toolName || typeof toolName !== "string") {
|
|
74524
74560
|
return { ok: false, reason: "missing tool_name" };
|
|
74525
74561
|
}
|
|
@@ -74543,16 +74579,17 @@ var require_claude_hook = __commonJS({
|
|
|
74543
74579
|
return false;
|
|
74544
74580
|
}
|
|
74545
74581
|
function deriveAfterContent(toolName, toolInput, diskBefore) {
|
|
74546
|
-
const name = String(toolName || "");
|
|
74547
|
-
if (name === "
|
|
74548
|
-
|
|
74582
|
+
const name = String(toolName || "").toLowerCase();
|
|
74583
|
+
if (name === "write") {
|
|
74584
|
+
const content = contentFromInput(toolInput);
|
|
74585
|
+
if (typeof content !== "string") {
|
|
74549
74586
|
return { ok: false, reason: "Write tool_input.content missing or not a string" };
|
|
74550
74587
|
}
|
|
74551
|
-
return { ok: true, after:
|
|
74588
|
+
return { ok: true, after: content };
|
|
74552
74589
|
}
|
|
74553
|
-
if (name === "
|
|
74554
|
-
const oldS = toolInput
|
|
74555
|
-
const newS = toolInput
|
|
74590
|
+
if (name === "edit") {
|
|
74591
|
+
const oldS = pickToolField(toolInput, "old_string", "oldString");
|
|
74592
|
+
const newS = pickToolField(toolInput, "new_string", "newString");
|
|
74556
74593
|
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
74557
74594
|
return { ok: false, reason: "Edit tool_input.old_string/new_string missing" };
|
|
74558
74595
|
}
|
|
@@ -74561,16 +74598,16 @@ var require_claude_hook = __commonJS({
|
|
|
74561
74598
|
}
|
|
74562
74599
|
return { ok: true, after: diskBefore.replace(oldS, newS) };
|
|
74563
74600
|
}
|
|
74564
|
-
if (name === "
|
|
74565
|
-
const edits = toolInput
|
|
74601
|
+
if (name === "multiedit" || name === "multi_edit") {
|
|
74602
|
+
const edits = pickToolField(toolInput, "edits", "Edits");
|
|
74566
74603
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
74567
74604
|
return { ok: false, reason: "MultiEdit tool_input.edits missing or empty" };
|
|
74568
74605
|
}
|
|
74569
74606
|
let cur = diskBefore;
|
|
74570
74607
|
for (let i = 0; i < edits.length; i++) {
|
|
74571
74608
|
const e = edits[i] || {};
|
|
74572
|
-
const oldS = e
|
|
74573
|
-
const newS = e
|
|
74609
|
+
const oldS = pickToolField(e, "old_string", "oldString");
|
|
74610
|
+
const newS = pickToolField(e, "new_string", "newString");
|
|
74574
74611
|
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
74575
74612
|
return { ok: false, reason: `MultiEdit edits[${i}] missing old_string/new_string` };
|
|
74576
74613
|
}
|
|
@@ -74581,7 +74618,7 @@ var require_claude_hook = __commonJS({
|
|
|
74581
74618
|
}
|
|
74582
74619
|
return { ok: true, after: cur };
|
|
74583
74620
|
}
|
|
74584
|
-
return { ok: false, reason: `unsupported tool_name for content derive: ${
|
|
74621
|
+
return { ok: false, reason: `unsupported tool_name for content derive: ${toolName}` };
|
|
74585
74622
|
}
|
|
74586
74623
|
function isV2DecisionBody(d, dr) {
|
|
74587
74624
|
if (dr && typeof dr === "object") return true;
|
|
@@ -74742,18 +74779,6 @@ var require_claude_hook = __commonJS({
|
|
|
74742
74779
|
}
|
|
74743
74780
|
return r;
|
|
74744
74781
|
};
|
|
74745
|
-
const apiKey = resolveApiKey({ ...deps, cwd, env });
|
|
74746
|
-
if (!apiKey) {
|
|
74747
|
-
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
74748
|
-
return emitTerminal(failClosedOrAdvisory({
|
|
74749
|
-
advisory,
|
|
74750
|
-
strict,
|
|
74751
|
-
site: "missing_api_key",
|
|
74752
|
-
why: "no API key",
|
|
74753
|
-
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
74754
|
-
errLog
|
|
74755
|
-
}));
|
|
74756
|
-
}
|
|
74757
74782
|
const PARSE_GAP_STDERR = "unparseable input \u2014 refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften";
|
|
74758
74783
|
const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
|
|
74759
74784
|
const parsed = parseStdinJson(raw);
|
|
@@ -74767,7 +74792,11 @@ var require_claude_hook = __commonJS({
|
|
|
74767
74792
|
}
|
|
74768
74793
|
const { toolName, toolInput } = parsed;
|
|
74769
74794
|
tool = toolName;
|
|
74770
|
-
|
|
74795
|
+
if (!isGovernedTool(toolName)) {
|
|
74796
|
+
logEv({ type: "detection_skip", signals: ["not_governed_tool"], tool });
|
|
74797
|
+
return emitTerminal({ exitCode: 0, reason: "not_governed_tool" });
|
|
74798
|
+
}
|
|
74799
|
+
const filePath = filePathFromInput(toolInput);
|
|
74771
74800
|
if (!filePath || typeof filePath !== "string") {
|
|
74772
74801
|
if (advisory && !strict) {
|
|
74773
74802
|
errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (CODERIFTS_ADVISORY)");
|
|
@@ -74782,6 +74811,18 @@ var require_claude_hook = __commonJS({
|
|
|
74782
74811
|
logEv({ type: "detection_skip", signals: ["not_spec_path"], tool, path: filePath });
|
|
74783
74812
|
return emitTerminal({ exitCode: 0, reason: "not_spec_path" });
|
|
74784
74813
|
}
|
|
74814
|
+
const apiKey = resolveApiKey({ ...deps, cwd, env });
|
|
74815
|
+
if (!apiKey) {
|
|
74816
|
+
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
74817
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
74818
|
+
advisory,
|
|
74819
|
+
strict,
|
|
74820
|
+
site: "missing_api_key",
|
|
74821
|
+
why: "no API key",
|
|
74822
|
+
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
74823
|
+
errLog
|
|
74824
|
+
}));
|
|
74825
|
+
}
|
|
74785
74826
|
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
74786
74827
|
let diskBefore = "";
|
|
74787
74828
|
if (exists(absPath)) {
|
|
@@ -74941,6 +74982,11 @@ var require_claude_hook = __commonJS({
|
|
|
74941
74982
|
module2.exports = {
|
|
74942
74983
|
runClaudeHook,
|
|
74943
74984
|
parseStdinJson,
|
|
74985
|
+
pickToolField,
|
|
74986
|
+
filePathFromInput,
|
|
74987
|
+
contentFromInput,
|
|
74988
|
+
isGovernedTool,
|
|
74989
|
+
coerceToolInput,
|
|
74944
74990
|
isSpecPath,
|
|
74945
74991
|
deriveAfterContent,
|
|
74946
74992
|
mapDecisionSeverity,
|
|
@@ -142589,6 +142635,13 @@ var require_key_store = __commonJS({
|
|
|
142589
142635
|
function findByPrefix(prefix) {
|
|
142590
142636
|
return keyRecords.find((r) => r.keyPrefix === prefix) || null;
|
|
142591
142637
|
}
|
|
142638
|
+
function findAllByPrefix(prefix) {
|
|
142639
|
+
if (!prefix || typeof prefix !== "string") return [];
|
|
142640
|
+
return keyRecords.filter((r) => r.keyPrefix === prefix);
|
|
142641
|
+
}
|
|
142642
|
+
function isFullKeyToken(token) {
|
|
142643
|
+
return typeof token === "string" && token.length > 12;
|
|
142644
|
+
}
|
|
142592
142645
|
function findRecordByKey(key) {
|
|
142593
142646
|
if (!key || typeof key !== "string") return null;
|
|
142594
142647
|
const prefix = getKeyPrefix(key);
|
|
@@ -142672,14 +142725,38 @@ var require_key_store = __commonJS({
|
|
|
142672
142725
|
function _setGenerateKeyStringForTest(fn) {
|
|
142673
142726
|
_generateKeyStringForTest = typeof fn === "function" ? fn : null;
|
|
142674
142727
|
}
|
|
142675
|
-
function
|
|
142676
|
-
const idx = keyRecords.
|
|
142728
|
+
function deleteRecord(record, ownerEmail) {
|
|
142729
|
+
const idx = keyRecords.indexOf(record);
|
|
142677
142730
|
if (idx === -1) return false;
|
|
142678
142731
|
if (ownerEmail && keyRecords[idx].owner !== ownerEmail) return false;
|
|
142679
142732
|
keyRecords.splice(idx, 1);
|
|
142680
142733
|
saveKeys();
|
|
142681
142734
|
return true;
|
|
142682
142735
|
}
|
|
142736
|
+
function revokeKey({ token, ownerEmail } = {}) {
|
|
142737
|
+
const raw = typeof token === "string" ? token : "";
|
|
142738
|
+
if (!raw) return { ok: false, code: "not_found", count: 0 };
|
|
142739
|
+
if (isFullKeyToken(raw)) {
|
|
142740
|
+
const rec = findRecordByKey(raw);
|
|
142741
|
+
if (!rec) return { ok: false, code: "not_found", count: 0 };
|
|
142742
|
+
if (!deleteRecord(rec, ownerEmail || null)) {
|
|
142743
|
+
return { ok: false, code: "unauthorized", count: 1, prefix: rec.keyPrefix };
|
|
142744
|
+
}
|
|
142745
|
+
return { ok: true, code: "deleted", count: 1, prefix: rec.keyPrefix };
|
|
142746
|
+
}
|
|
142747
|
+
const matches = findAllByPrefix(raw);
|
|
142748
|
+
if (matches.length === 0) return { ok: false, code: "not_found", count: 0 };
|
|
142749
|
+
if (matches.length > 1) {
|
|
142750
|
+
return { ok: false, code: "ambiguous", count: matches.length, prefix: raw };
|
|
142751
|
+
}
|
|
142752
|
+
if (!deleteRecord(matches[0], ownerEmail || null)) {
|
|
142753
|
+
return { ok: false, code: "unauthorized", count: 1, prefix: raw };
|
|
142754
|
+
}
|
|
142755
|
+
return { ok: true, code: "deleted", count: 1, prefix: raw };
|
|
142756
|
+
}
|
|
142757
|
+
function deleteByPrefix(prefix, ownerEmail) {
|
|
142758
|
+
return revokeKey({ token: prefix, ownerEmail }).ok === true;
|
|
142759
|
+
}
|
|
142683
142760
|
module2.exports = {
|
|
142684
142761
|
createKey,
|
|
142685
142762
|
hasKey,
|
|
@@ -142689,7 +142766,9 @@ var require_key_store = __commonJS({
|
|
|
142689
142766
|
listKeysByOwner,
|
|
142690
142767
|
deleteByPrefix,
|
|
142691
142768
|
findByPrefix,
|
|
142769
|
+
findAllByPrefix,
|
|
142692
142770
|
findRecordByKey,
|
|
142771
|
+
revokeKey,
|
|
142693
142772
|
FREE_TIER_REQUESTS_PER_MONTH,
|
|
142694
142773
|
FREE_TIER_REQUESTS_PER_MINUTE,
|
|
142695
142774
|
PREFIX_REGENERATE_ATTEMPTS,
|
|
@@ -198815,9 +198894,10 @@ var require_remediation_taxonomy = __commonJS({
|
|
|
198815
198894
|
break;
|
|
198816
198895
|
}
|
|
198817
198896
|
case "COVERAGE_GAP": {
|
|
198818
|
-
|
|
198897
|
+
const detail = str(pattern && pattern.description) || str(pattern && pattern.error);
|
|
198898
|
+
target = str(pattern && pattern.affected_path) || "analysis";
|
|
198819
198899
|
target_ref = {};
|
|
198820
|
-
instruction = "Do not treat this as a schema edit: restore analyzable artifacts / wait for coverage; re-preflight when analysis is complete.";
|
|
198900
|
+
instruction = detail ? `fix the spec: ${detail}` : "Do not treat this as a schema edit: restore analyzable artifacts / wait for coverage; re-preflight when analysis is complete.";
|
|
198821
198901
|
break;
|
|
198822
198902
|
}
|
|
198823
198903
|
default: {
|
|
@@ -198872,6 +198952,21 @@ var require_remediation_taxonomy = __commonJS({
|
|
|
198872
198952
|
if (patterns.length > 0) {
|
|
198873
198953
|
return patterns.map((pattern) => remediationFromDetection({ pattern, ir: null }));
|
|
198874
198954
|
}
|
|
198955
|
+
const degradedReasons = Array.isArray(verdict.degraded_reasons) ? verdict.degraded_reasons : [];
|
|
198956
|
+
if (degradedReasons.length > 0) {
|
|
198957
|
+
return degradedReasons.map((r) => {
|
|
198958
|
+
const msg = r && (r.message || r.code) ? String(r.message || r.code) : "analysis incomplete";
|
|
198959
|
+
return remediationFromDetection({
|
|
198960
|
+
pattern: {
|
|
198961
|
+
name: "COVERAGE_GAP",
|
|
198962
|
+
degraded: true,
|
|
198963
|
+
description: msg,
|
|
198964
|
+
affected_path: r && r.code || "analysis"
|
|
198965
|
+
},
|
|
198966
|
+
ir: null
|
|
198967
|
+
});
|
|
198968
|
+
});
|
|
198969
|
+
}
|
|
198875
198970
|
if (verdict.coverage_gap === true || verdict.degraded === true) {
|
|
198876
198971
|
return [remediationFromDetection({ pattern: { name: "COVERAGE_GAP", degraded: true }, ir: null })];
|
|
198877
198972
|
}
|
|
@@ -200794,9 +200889,20 @@ var require_decision_result_v1_producer = __commonJS({
|
|
|
200794
200889
|
source: {
|
|
200795
200890
|
type: "string",
|
|
200796
200891
|
enum: [
|
|
200797
|
-
"github_compare"
|
|
200892
|
+
"github_compare",
|
|
200893
|
+
"gitlab_compare",
|
|
200894
|
+
"bitbucket_compare"
|
|
200798
200895
|
],
|
|
200799
|
-
description: "How the server listed the change-set. github_compare = GitHub Compare API via the App installation."
|
|
200896
|
+
description: "How the server listed the change-set. github_compare = GitHub Compare API via the App installation. gitlab_compare / bitbucket_compare = platform Compare via a caller-supplied short-lived SCM token (never stored)."
|
|
200897
|
+
},
|
|
200898
|
+
platform: {
|
|
200899
|
+
type: "string",
|
|
200900
|
+
enum: [
|
|
200901
|
+
"github",
|
|
200902
|
+
"gitlab",
|
|
200903
|
+
"bitbucket"
|
|
200904
|
+
],
|
|
200905
|
+
description: "Additive P1-6. SCM platform that produced this SERVER_DERIVED set. Taken from the proven binding / context.platform \u2014 never guessed from the repository string. Absent on envelopes minted before this field existed."
|
|
200800
200906
|
},
|
|
200801
200907
|
base_sha: {
|
|
200802
200908
|
type: "string",
|
|
@@ -201704,6 +201810,9 @@ var require_change_set_completeness = __commonJS({
|
|
|
201704
201810
|
if (v == null) return "";
|
|
201705
201811
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
201706
201812
|
}
|
|
201813
|
+
function afterContentHash(content) {
|
|
201814
|
+
return sha256hexLocal(specStr(content));
|
|
201815
|
+
}
|
|
201707
201816
|
function artifactPath(artifact) {
|
|
201708
201817
|
if (!artifact || typeof artifact !== "object") return "";
|
|
201709
201818
|
if (typeof artifact.path === "string" && artifact.path.trim()) return artifact.path.trim();
|
|
@@ -202002,7 +202111,8 @@ var require_change_set_completeness = __commonJS({
|
|
|
202002
202111
|
contract_selector_hash,
|
|
202003
202112
|
completeness_count: normalizedLeaves.length,
|
|
202004
202113
|
submitted_set_digest,
|
|
202005
|
-
expected_channel: "webhook_full_file_list"
|
|
202114
|
+
expected_channel: "webhook_full_file_list",
|
|
202115
|
+
leaves: normalizedLeaves
|
|
202006
202116
|
};
|
|
202007
202117
|
}
|
|
202008
202118
|
function assertCompletenessRequirement(block, requireLevel) {
|
|
@@ -202086,9 +202196,45 @@ var require_change_set_completeness = __commonJS({
|
|
|
202086
202196
|
contract_selector_hash: computeContractSelectorHash(DEFAULT_CONTRACT_SELECTOR),
|
|
202087
202197
|
completeness_count: leaves.length,
|
|
202088
202198
|
submitted_set_digest,
|
|
202089
|
-
expected_channel: null
|
|
202199
|
+
expected_channel: null,
|
|
202200
|
+
leaves
|
|
202090
202201
|
};
|
|
202091
202202
|
}
|
|
202203
|
+
function authorizedPathsFromArtifacts(artifacts) {
|
|
202204
|
+
const list = Array.isArray(artifacts) ? artifacts : [];
|
|
202205
|
+
const seen = /* @__PURE__ */ new Set();
|
|
202206
|
+
const rows = [];
|
|
202207
|
+
for (const a of list) {
|
|
202208
|
+
const path = artifactPath(a);
|
|
202209
|
+
if (!path || seen.has(path)) continue;
|
|
202210
|
+
seen.add(path);
|
|
202211
|
+
rows.push({
|
|
202212
|
+
path,
|
|
202213
|
+
after_hex: sha256hexLocal(specStr(a && a.after)),
|
|
202214
|
+
change_type: deriveChangeType(a && a.before, a && a.after)
|
|
202215
|
+
});
|
|
202216
|
+
}
|
|
202217
|
+
return rows;
|
|
202218
|
+
}
|
|
202219
|
+
function authorizedPathsForPersist(block, artifacts) {
|
|
202220
|
+
const submitted = authorizedPathsFromArtifacts(artifacts);
|
|
202221
|
+
const leaves = block && Array.isArray(block.leaves) ? block.leaves : null;
|
|
202222
|
+
if (!leaves || leaves.length === 0) return submitted;
|
|
202223
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
202224
|
+
for (const L of leaves) {
|
|
202225
|
+
if (!L || typeof L !== "object") continue;
|
|
202226
|
+
const path = String(L.path == null ? "" : L.path).trim();
|
|
202227
|
+
if (!path) continue;
|
|
202228
|
+
const after_hex = String(L.after_hex == null ? "" : L.after_hex).replace(/^sha256:/i, "").toLowerCase();
|
|
202229
|
+
if (!/^[0-9a-f]{64}$/.test(after_hex)) continue;
|
|
202230
|
+
byPath.set(path, {
|
|
202231
|
+
path,
|
|
202232
|
+
after_hex,
|
|
202233
|
+
change_type: normalizeChangeType(L.change_type)
|
|
202234
|
+
});
|
|
202235
|
+
}
|
|
202236
|
+
return submitted.map((row) => byPath.get(row.path) || row);
|
|
202237
|
+
}
|
|
202092
202238
|
function completenessToEnvelopeFields(block) {
|
|
202093
202239
|
if (!block || block.completeness_mode === "UNBOUND") {
|
|
202094
202240
|
return {
|
|
@@ -202135,7 +202281,10 @@ var require_change_set_completeness = __commonJS({
|
|
|
202135
202281
|
assertNoBooleanCompleteness,
|
|
202136
202282
|
completenessRequiredError,
|
|
202137
202283
|
sha256hexLocal,
|
|
202138
|
-
specStr
|
|
202284
|
+
specStr,
|
|
202285
|
+
afterContentHash,
|
|
202286
|
+
authorizedPathsFromArtifacts,
|
|
202287
|
+
authorizedPathsForPersist
|
|
202139
202288
|
};
|
|
202140
202289
|
}
|
|
202141
202290
|
});
|
|
@@ -202700,7 +202849,8 @@ var require_change_set = __commonJS({
|
|
|
202700
202849
|
assertCompletenessRequirement,
|
|
202701
202850
|
completenessToEnvelopeFields,
|
|
202702
202851
|
applyProvenRepoBinding,
|
|
202703
|
-
assertNoBooleanCompleteness
|
|
202852
|
+
assertNoBooleanCompleteness,
|
|
202853
|
+
authorizedPathsForPersist
|
|
202704
202854
|
} = require_change_set_completeness();
|
|
202705
202855
|
var { authorFingerprintBindingEnvelopeFields } = require_fingerprint_binding_gate();
|
|
202706
202856
|
var NUL = "";
|
|
@@ -203427,6 +203577,7 @@ var require_change_set = __commonJS({
|
|
|
203427
203577
|
}
|
|
203428
203578
|
}
|
|
203429
203579
|
const completenessEnvelope = completenessToEnvelopeFields(completenessBlock);
|
|
203580
|
+
const authorizedPaths = preflightMode === "authorize" ? authorizedPathsForPersist(completenessBlock, artifacts) : [];
|
|
203430
203581
|
const fingerprintBindingEnvelope = authorFingerprintBindingEnvelopeFields({
|
|
203431
203582
|
preflightMode
|
|
203432
203583
|
});
|
|
@@ -203488,6 +203639,7 @@ var require_change_set = __commonJS({
|
|
|
203488
203639
|
// body_hash stays byte-identical. Covered by body_hash when set; NOT in fingerprint.
|
|
203489
203640
|
...input && input.derivation_meta && typeof input.derivation_meta === "object" ? { derivation: {
|
|
203490
203641
|
source: String(input.derivation_meta.source || "github_compare"),
|
|
203642
|
+
platform: String(input.derivation_meta.platform || "github"),
|
|
203491
203643
|
base_sha: String(input.derivation_meta.base_sha || ""),
|
|
203492
203644
|
head_sha: String(input.derivation_meta.head_sha || "")
|
|
203493
203645
|
} } : {},
|
|
@@ -203682,6 +203834,12 @@ var require_change_set = __commonJS({
|
|
|
203682
203834
|
}
|
|
203683
203835
|
} catch (_) {
|
|
203684
203836
|
}
|
|
203837
|
+
if (preflightMode === "authorize" && Array.isArray(authorizedPaths) && authorizedPaths.length > 0) {
|
|
203838
|
+
Object.defineProperty(out, "_authorized_paths", {
|
|
203839
|
+
value: Object.freeze(authorizedPaths.map((r) => Object.freeze({ ...r }))),
|
|
203840
|
+
enumerable: false
|
|
203841
|
+
});
|
|
203842
|
+
}
|
|
203685
203843
|
return out;
|
|
203686
203844
|
}
|
|
203687
203845
|
module2.exports = {
|
|
@@ -206628,8 +206786,19 @@ var require_remediation_transaction = __commonJS({
|
|
|
206628
206786
|
if (artifactIds.length > 0) scope.artifact_ids = artifactIds;
|
|
206629
206787
|
return scope;
|
|
206630
206788
|
}
|
|
206789
|
+
function isDegradedContext(coreVerdict, context = {}) {
|
|
206790
|
+
if (context.analysis_complete === false) return true;
|
|
206791
|
+
if (Array.isArray(context.degraded_reasons) && context.degraded_reasons.length > 0) return true;
|
|
206792
|
+
if (coreVerdict && coreVerdict.degraded === true) return true;
|
|
206793
|
+
if (Array.isArray(coreVerdict && coreVerdict.degraded_reasons) && coreVerdict.degraded_reasons.length > 0) {
|
|
206794
|
+
return true;
|
|
206795
|
+
}
|
|
206796
|
+
return false;
|
|
206797
|
+
}
|
|
206631
206798
|
function buildRemediationTransaction({ decision, fingerprint, coreVerdict, context = {} }) {
|
|
206632
|
-
|
|
206799
|
+
const degraded = isDegradedContext(coreVerdict, context);
|
|
206800
|
+
const emit = decision === "BLOCK" || decision === "REQUIRE_APPROVAL" || degraded;
|
|
206801
|
+
if (!emit) return null;
|
|
206633
206802
|
if (typeof fingerprint !== "string" || !fingerprint) return null;
|
|
206634
206803
|
const verdictForTaxonomy = {
|
|
206635
206804
|
...coreVerdict && typeof coreVerdict === "object" ? coreVerdict : {}
|
|
@@ -206639,6 +206808,13 @@ var require_remediation_transaction = __commonJS({
|
|
|
206639
206808
|
verdictForTaxonomy.detected_patterns = context.detectedPatterns;
|
|
206640
206809
|
}
|
|
206641
206810
|
}
|
|
206811
|
+
if (degraded) {
|
|
206812
|
+
verdictForTaxonomy.degraded = true;
|
|
206813
|
+
const reasons = Array.isArray(context.degraded_reasons) && context.degraded_reasons.length ? context.degraded_reasons : verdictForTaxonomy.degraded_reasons;
|
|
206814
|
+
if (Array.isArray(reasons) && reasons.length) {
|
|
206815
|
+
verdictForTaxonomy.degraded_reasons = reasons;
|
|
206816
|
+
}
|
|
206817
|
+
}
|
|
206642
206818
|
const required_changes = buildRemediations(verdictForTaxonomy);
|
|
206643
206819
|
const profile = resolveFingerprintProfile(context);
|
|
206644
206820
|
return {
|
|
@@ -206673,6 +206849,7 @@ var require_remediation_transaction = __commonJS({
|
|
|
206673
206849
|
resolveFingerprintProfile,
|
|
206674
206850
|
deriveRecheckScope,
|
|
206675
206851
|
blockRequiredActionCore,
|
|
206852
|
+
isDegradedContext,
|
|
206676
206853
|
PROFILE_CRBUNDLE_V1,
|
|
206677
206854
|
PROFILE_VERDICT_FP_V1,
|
|
206678
206855
|
RESUBMISSION_UNCHANGED,
|
|
@@ -206848,7 +207025,7 @@ var require_decision_result = __commonJS({
|
|
|
206848
207025
|
pattern_sources: null,
|
|
206849
207026
|
// Control core for retrieval; null = not supplied (must never render as type none for BLOCK)
|
|
206850
207027
|
required_action_core: null,
|
|
206851
|
-
// ID850 v1 —
|
|
207028
|
+
// ID850 v1 — remediation transaction (null / omit on ALLOW/WARN with complete analysis)
|
|
206852
207029
|
remediation_transaction: null,
|
|
206853
207030
|
// ID637 v5 slice 4 — change-set completeness commitment (honest; not a proof on generic path).
|
|
206854
207031
|
// SERVER-authored mode; body_hash covered. Never boolean completeness:true.
|
|
@@ -209804,6 +209981,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209804
209981
|
id: "claude",
|
|
209805
209982
|
relPath: ".mcp.json",
|
|
209806
209983
|
label: "Claude Code",
|
|
209984
|
+
rootKey: "mcpServers",
|
|
209807
209985
|
// Measured: type+url required. url-without-type is a config error.
|
|
209808
209986
|
entry: Object.freeze({
|
|
209809
209987
|
type: "http",
|
|
@@ -209818,6 +209996,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209818
209996
|
id: "cursor",
|
|
209819
209997
|
relPath: ".cursor/mcp.json",
|
|
209820
209998
|
label: "Cursor",
|
|
209999
|
+
rootKey: "mcpServers",
|
|
209821
210000
|
// Measured: url only — Cursor docs do not use type for remote HTTP.
|
|
209822
210001
|
entry: Object.freeze({
|
|
209823
210002
|
url: MCP_URL,
|
|
@@ -209826,8 +210005,24 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209826
210005
|
})
|
|
209827
210006
|
}),
|
|
209828
210007
|
shapeNote: "mcpServers.coderifts url (no type \u2014 Cursor remote form)"
|
|
210008
|
+
}),
|
|
210009
|
+
copilot: Object.freeze({
|
|
210010
|
+
id: "copilot",
|
|
210011
|
+
relPath: ".vscode/mcp.json",
|
|
210012
|
+
label: "GitHub Copilot / VS Code",
|
|
210013
|
+
// Measured: THIRD variant — root key is `servers`, not mcpServers.
|
|
210014
|
+
rootKey: "servers",
|
|
210015
|
+
entry: Object.freeze({
|
|
210016
|
+
type: "http",
|
|
210017
|
+
url: MCP_URL,
|
|
210018
|
+
headers: Object.freeze({
|
|
210019
|
+
Authorization: "Bearer ${env:CODERIFTS_API_KEY}"
|
|
210020
|
+
})
|
|
210021
|
+
}),
|
|
210022
|
+
shapeNote: "servers.coderifts type=http url (root key servers, not mcpServers)"
|
|
209829
210023
|
})
|
|
209830
210024
|
});
|
|
210025
|
+
var MCP_HOST_IDS = Object.freeze(Object.keys(MCP_HOSTS));
|
|
209831
210026
|
function deepEqual(a, b) {
|
|
209832
210027
|
if (a === b) return true;
|
|
209833
210028
|
try {
|
|
@@ -209836,25 +210031,26 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209836
210031
|
return false;
|
|
209837
210032
|
}
|
|
209838
210033
|
}
|
|
209839
|
-
function planMcpMerge(existing, entry) {
|
|
210034
|
+
function planMcpMerge(existing, entry, rootKey = "mcpServers") {
|
|
210035
|
+
const key = rootKey || "mcpServers";
|
|
209840
210036
|
const hadFile = existing != null;
|
|
209841
210037
|
const base = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
209842
|
-
const
|
|
209843
|
-
const current = Object.prototype.hasOwnProperty.call(
|
|
210038
|
+
const group = base[key] && typeof base[key] === "object" && !Array.isArray(base[key]) ? base[key] : {};
|
|
210039
|
+
const current = Object.prototype.hasOwnProperty.call(group, SERVER_NAME) ? group[SERVER_NAME] : void 0;
|
|
209844
210040
|
if (deepEqual(current, entry)) {
|
|
209845
210041
|
return { action: "skip", next: null, reason: "already present, identical" };
|
|
209846
210042
|
}
|
|
209847
210043
|
const next = {
|
|
209848
210044
|
...base,
|
|
209849
|
-
|
|
209850
|
-
...
|
|
210045
|
+
[key]: {
|
|
210046
|
+
...group,
|
|
209851
210047
|
[SERVER_NAME]: entry
|
|
209852
210048
|
}
|
|
209853
210049
|
};
|
|
209854
210050
|
if (!hadFile) {
|
|
209855
210051
|
return { action: "create", next, reason: "created" };
|
|
209856
210052
|
}
|
|
209857
|
-
const reason = current === void 0 ?
|
|
210053
|
+
const reason = current === void 0 ? `added ${key}.coderifts; preserved other servers` : `updated ${key}.coderifts; preserved other servers`;
|
|
209858
210054
|
return { action: "merge", next, reason };
|
|
209859
210055
|
}
|
|
209860
210056
|
function serializeMcp(doc) {
|
|
@@ -209923,7 +210119,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209923
210119
|
existing = {};
|
|
209924
210120
|
}
|
|
209925
210121
|
}
|
|
209926
|
-
const plan = planMcpMerge(existing, host.entry);
|
|
210122
|
+
const plan = planMcpMerge(existing, host.entry, host.rootKey);
|
|
209927
210123
|
if (plan.action === "skip") {
|
|
209928
210124
|
return { action: "skip", relPath: host.relPath, reason: plan.reason, exitCode: 0 };
|
|
209929
210125
|
}
|
|
@@ -209960,7 +210156,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209960
210156
|
if (!exists(fp)) return false;
|
|
209961
210157
|
try {
|
|
209962
210158
|
const doc = JSON.parse(readFile(fp));
|
|
209963
|
-
return planMcpMerge(doc, host.entry).action === "skip";
|
|
210159
|
+
return planMcpMerge(doc, host.entry, host.rootKey).action === "skip";
|
|
209964
210160
|
} catch {
|
|
209965
210161
|
return false;
|
|
209966
210162
|
}
|
|
@@ -209969,6 +210165,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209969
210165
|
MCP_URL,
|
|
209970
210166
|
SERVER_NAME,
|
|
209971
210167
|
MCP_HOSTS,
|
|
210168
|
+
MCP_HOST_IDS,
|
|
209972
210169
|
planMcpMerge,
|
|
209973
210170
|
serializeMcp,
|
|
209974
210171
|
writeHostMcpConfig,
|
|
@@ -210084,6 +210281,7 @@ var require_init_agents = __commonJS({
|
|
|
210084
210281
|
var { installClaudeHook, installCursorHook } = require_hook();
|
|
210085
210282
|
var {
|
|
210086
210283
|
MCP_HOSTS,
|
|
210284
|
+
MCP_HOST_IDS,
|
|
210087
210285
|
writeHostMcpConfig,
|
|
210088
210286
|
mcpConfigPresent
|
|
210089
210287
|
} = require_mcp_repo_config();
|
|
@@ -210095,15 +210293,15 @@ var require_init_agents = __commonJS({
|
|
|
210095
210293
|
workflowPresent
|
|
210096
210294
|
} = require_contract_gate_workflow();
|
|
210097
210295
|
if (process.env.NO_COLOR) chalk.level = 0;
|
|
210098
|
-
var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
|
|
210296
|
+
var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,copilot,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
|
|
210099
210297
|
|
|
210100
210298
|
Wire this repository for governed agent work in ONE command:
|
|
210101
|
-
1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json)
|
|
210299
|
+
1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json / .vscode/mcp.json)
|
|
210102
210300
|
2. generated agent rule files (reuses agent-setup)
|
|
210103
|
-
3. host hook (Claude Code
|
|
210301
|
+
3. host hook (Claude Code / VS Code Copilot via .claude/settings.json; Cursor via .cursor/hooks.json)
|
|
210104
210302
|
4. .github/workflows/coderifts.yml (coderifts/contract-gate@v0)
|
|
210105
210303
|
|
|
210106
|
-
--hosts <list> claude, cursor, or all (default: all)
|
|
210304
|
+
--hosts <list> claude, cursor, copilot, or all (default: all)
|
|
210107
210305
|
--no-hook Skip host hook install
|
|
210108
210306
|
--no-workflow Skip writing the CI workflow
|
|
210109
210307
|
--dry-run Print the plan without writing
|
|
@@ -210114,7 +210312,8 @@ var require_init_agents = __commonJS({
|
|
|
210114
210312
|
var SHARED_RULE = "AGENTS.md";
|
|
210115
210313
|
var HOST_RULES = Object.freeze({
|
|
210116
210314
|
claude: Object.freeze(["CLAUDE.md"]),
|
|
210117
|
-
cursor: Object.freeze([".cursor/rules/coderifts.mdc"])
|
|
210315
|
+
cursor: Object.freeze([".cursor/rules/coderifts.mdc"]),
|
|
210316
|
+
copilot: Object.freeze([".github/copilot-instructions.md"])
|
|
210118
210317
|
});
|
|
210119
210318
|
var EXTRA_RULES_ON_ALL = Object.freeze([
|
|
210120
210319
|
".github/copilot-instructions.md",
|
|
@@ -210134,27 +210333,28 @@ var require_init_agents = __commonJS({
|
|
|
210134
210333
|
" \xB7 write Gemini MCP config (Gemini uses httpUrl at extension level, not a repo file)"
|
|
210135
210334
|
]);
|
|
210136
210335
|
function parseHosts(raw) {
|
|
210336
|
+
const allHosts = [...MCP_HOST_IDS];
|
|
210137
210337
|
if (raw == null || raw === true || String(raw).trim() === "") {
|
|
210138
|
-
return { hosts:
|
|
210338
|
+
return { hosts: allHosts, allRules: true };
|
|
210139
210339
|
}
|
|
210140
210340
|
const parts = String(raw).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
210141
210341
|
if (parts.length === 0) {
|
|
210142
|
-
return { hosts:
|
|
210342
|
+
return { hosts: allHosts, allRules: true, error: `init --agents: --hosts requires a value
|
|
210143
210343
|
${USAGE}` };
|
|
210144
210344
|
}
|
|
210145
|
-
const allowed = /* @__PURE__ */ new Set([
|
|
210345
|
+
const allowed = /* @__PURE__ */ new Set([...MCP_HOST_IDS, "all"]);
|
|
210146
210346
|
for (const p of parts) {
|
|
210147
210347
|
if (!allowed.has(p)) {
|
|
210148
210348
|
return {
|
|
210149
210349
|
hosts: [],
|
|
210150
210350
|
allRules: false,
|
|
210151
|
-
error: `init --agents: unknown host "${p}" (use claude, cursor, all)
|
|
210351
|
+
error: `init --agents: unknown host "${p}" (use claude, cursor, copilot, all)
|
|
210152
210352
|
${USAGE}`
|
|
210153
210353
|
};
|
|
210154
210354
|
}
|
|
210155
210355
|
}
|
|
210156
210356
|
if (parts.includes("all")) {
|
|
210157
|
-
return { hosts:
|
|
210357
|
+
return { hosts: allHosts, allRules: true };
|
|
210158
210358
|
}
|
|
210159
210359
|
const hosts = [];
|
|
210160
210360
|
for (const p of parts) {
|
|
@@ -210186,7 +210386,25 @@ ${USAGE}`
|
|
|
210186
210386
|
return hostId === "cursor" ? ".cursor/hooks.json" : ".claude/settings.json";
|
|
210187
210387
|
}
|
|
210188
210388
|
function hookNoteForHost(hostId) {
|
|
210189
|
-
|
|
210389
|
+
if (hostId === "cursor") return "preToolUse \u2192 coderifts cursor-hook";
|
|
210390
|
+
if (hostId === "copilot") {
|
|
210391
|
+
return "PreToolUse \u2192 coderifts claude-hook (VS Code reads .claude/settings.json by default)";
|
|
210392
|
+
}
|
|
210393
|
+
return "PreToolUse \u2192 coderifts claude-hook";
|
|
210394
|
+
}
|
|
210395
|
+
function hookInstallerForHost(hostId) {
|
|
210396
|
+
return hostId === "cursor" ? installCursorHook : installClaudeHook;
|
|
210397
|
+
}
|
|
210398
|
+
function uniqueHookHosts(hosts) {
|
|
210399
|
+
const seen = /* @__PURE__ */ new Set();
|
|
210400
|
+
const out = [];
|
|
210401
|
+
for (const h of hosts) {
|
|
210402
|
+
const rel = hookRelForHost(h);
|
|
210403
|
+
if (seen.has(rel)) continue;
|
|
210404
|
+
seen.add(rel);
|
|
210405
|
+
out.push(h);
|
|
210406
|
+
}
|
|
210407
|
+
return out;
|
|
210190
210408
|
}
|
|
210191
210409
|
function wantHook(options) {
|
|
210192
210410
|
if (options.noHook === true) return false;
|
|
@@ -210233,9 +210451,9 @@ ${USAGE}`
|
|
|
210233
210451
|
const hooksMissing = [];
|
|
210234
210452
|
const hooksWanted = wantHook(options);
|
|
210235
210453
|
if (hooksWanted) {
|
|
210236
|
-
for (const h of hosts) {
|
|
210454
|
+
for (const h of uniqueHookHosts(hosts)) {
|
|
210237
210455
|
const rel = hookRelForHost(h);
|
|
210238
|
-
const installer = h
|
|
210456
|
+
const installer = hookInstallerForHost(h);
|
|
210239
210457
|
const r = installer({ cwd: outDir, silent: true, dryRun: true });
|
|
210240
210458
|
if (r && r.code === "NOOP") hooksPresent.push(rel);
|
|
210241
210459
|
else hooksMissing.push(rel);
|
|
@@ -210366,7 +210584,7 @@ ${USAGE}`
|
|
|
210366
210584
|
items.push({ section: "rules", action: "skip", relPath: rel, note: "exists; installer does not overwrite" });
|
|
210367
210585
|
}
|
|
210368
210586
|
if (!wantHook(options)) {
|
|
210369
|
-
for (const h of hosts) {
|
|
210587
|
+
for (const h of uniqueHookHosts(hosts)) {
|
|
210370
210588
|
items.push({
|
|
210371
210589
|
section: "hooks",
|
|
210372
210590
|
action: "skip",
|
|
@@ -210375,8 +210593,8 @@ ${USAGE}`
|
|
|
210375
210593
|
});
|
|
210376
210594
|
}
|
|
210377
210595
|
} else {
|
|
210378
|
-
for (const h of hosts) {
|
|
210379
|
-
const installer = h
|
|
210596
|
+
for (const h of uniqueHookHosts(hosts)) {
|
|
210597
|
+
const installer = hookInstallerForHost(h);
|
|
210380
210598
|
const r = installer({
|
|
210381
210599
|
cwd: outDir,
|
|
210382
210600
|
silent: true,
|
|
@@ -237012,6 +237230,162 @@ var require_status = __commonJS({
|
|
|
237012
237230
|
}
|
|
237013
237231
|
});
|
|
237014
237232
|
|
|
237233
|
+
// src/provider/workflow-scan.js
|
|
237234
|
+
var require_workflow_scan = __commonJS({
|
|
237235
|
+
"src/provider/workflow-scan.js"(exports2, module2) {
|
|
237236
|
+
"use strict";
|
|
237237
|
+
var yaml = require_js_yaml();
|
|
237238
|
+
var CONTRACT_GATE_ACTION = "coderifts/contract-gate";
|
|
237239
|
+
var DEPLOY_GATE_INVOCATION = /^(?:npx(?:\s+--yes)?\s+|npm\s+exec\s+|pnpm\s+(?:dlx|exec)\s+|yarn(?:\s+(?:dlx|exec))?\s+)?coderifts\s+deploy-gate(?:\s|$)/;
|
|
237240
|
+
var NON_SHELL = /* @__PURE__ */ new Set(["python", "python3", "node", "ruby", "perl", "php", "pwsh", "powershell"]);
|
|
237241
|
+
function isPinnedMajor(ref) {
|
|
237242
|
+
const r = String(ref || "");
|
|
237243
|
+
if (/^v\d+(\.\d+){0,2}$/.test(r)) return true;
|
|
237244
|
+
if (/^[0-9a-f]{40}$/i.test(r)) return true;
|
|
237245
|
+
return false;
|
|
237246
|
+
}
|
|
237247
|
+
function parseActionUses(uses) {
|
|
237248
|
+
const raw = String(uses || "").trim();
|
|
237249
|
+
const at = raw.lastIndexOf("@");
|
|
237250
|
+
if (at <= 0) return { name: raw, ref: "", pinnedMajor: false };
|
|
237251
|
+
return {
|
|
237252
|
+
name: raw.slice(0, at),
|
|
237253
|
+
ref: raw.slice(at + 1),
|
|
237254
|
+
pinnedMajor: isPinnedMajor(raw.slice(at + 1))
|
|
237255
|
+
};
|
|
237256
|
+
}
|
|
237257
|
+
function isContractGateUses(uses) {
|
|
237258
|
+
const parsed = parseActionUses(uses);
|
|
237259
|
+
return parsed.name === CONTRACT_GATE_ACTION && parsed.pinnedMajor === true;
|
|
237260
|
+
}
|
|
237261
|
+
function isExplicitlyDisabled(node) {
|
|
237262
|
+
if (!node || typeof node !== "object" || !Object.prototype.hasOwnProperty.call(node, "if")) {
|
|
237263
|
+
return false;
|
|
237264
|
+
}
|
|
237265
|
+
const ifVal = node.if;
|
|
237266
|
+
if (ifVal === false || ifVal === 0 || ifVal === null) return true;
|
|
237267
|
+
if (typeof ifVal === "string") {
|
|
237268
|
+
const s = ifVal.trim().toLowerCase();
|
|
237269
|
+
if (s === "false" || s === "0" || s === "" || s === "null") return true;
|
|
237270
|
+
if (/^\$\{\{\s*(?:false|0|null)\s*\}\}$/.test(s)) return true;
|
|
237271
|
+
}
|
|
237272
|
+
return false;
|
|
237273
|
+
}
|
|
237274
|
+
function jobEnvironmentName(job) {
|
|
237275
|
+
if (!job || typeof job !== "object") return null;
|
|
237276
|
+
const env = job.environment;
|
|
237277
|
+
if (typeof env === "string") {
|
|
237278
|
+
const name = env.trim();
|
|
237279
|
+
return name || null;
|
|
237280
|
+
}
|
|
237281
|
+
if (env && typeof env === "object" && typeof env.name === "string") {
|
|
237282
|
+
const name = env.name.trim();
|
|
237283
|
+
return name || null;
|
|
237284
|
+
}
|
|
237285
|
+
return null;
|
|
237286
|
+
}
|
|
237287
|
+
function isNonPosixShell(shell) {
|
|
237288
|
+
if (shell == null || shell === "") return false;
|
|
237289
|
+
const first = String(shell).trim().split(/\s+/)[0].replace(/^.*\//, "").toLowerCase();
|
|
237290
|
+
return NON_SHELL.has(first);
|
|
237291
|
+
}
|
|
237292
|
+
function isDeployGateInvocation(run, shell) {
|
|
237293
|
+
if (isNonPosixShell(shell)) return false;
|
|
237294
|
+
const text = String(run || "");
|
|
237295
|
+
let heredoc = null;
|
|
237296
|
+
let quote = null;
|
|
237297
|
+
for (const rawLine of text.split("\n")) {
|
|
237298
|
+
let line = String(rawLine || "");
|
|
237299
|
+
if (heredoc) {
|
|
237300
|
+
if (line.trim() === heredoc) heredoc = null;
|
|
237301
|
+
continue;
|
|
237302
|
+
}
|
|
237303
|
+
const hd = /<<[-]?\s*['"]?(\w+)['"]?/.exec(line);
|
|
237304
|
+
if (hd) heredoc = hd[1];
|
|
237305
|
+
if (quote) {
|
|
237306
|
+
for (let i = 0; i < line.length; i++) {
|
|
237307
|
+
const ch = line[i];
|
|
237308
|
+
if (ch === quote && line[i - 1] !== "\\") {
|
|
237309
|
+
quote = null;
|
|
237310
|
+
break;
|
|
237311
|
+
}
|
|
237312
|
+
}
|
|
237313
|
+
if (quote) continue;
|
|
237314
|
+
}
|
|
237315
|
+
const trimmed = line.trim();
|
|
237316
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
237317
|
+
let cmd = trimmed.replace(/^(?:\w+=\S+\s+)+/, "");
|
|
237318
|
+
if (/^(echo|printf|cat|false|true|:)\b/.test(cmd)) {
|
|
237319
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
237320
|
+
const ch = cmd[i];
|
|
237321
|
+
if ((ch === "'" || ch === '"') && cmd[i - 1] !== "\\") {
|
|
237322
|
+
quote = quote ? quote === ch ? null : quote : ch;
|
|
237323
|
+
}
|
|
237324
|
+
}
|
|
237325
|
+
continue;
|
|
237326
|
+
}
|
|
237327
|
+
if (DEPLOY_GATE_INVOCATION.test(cmd)) return true;
|
|
237328
|
+
}
|
|
237329
|
+
return false;
|
|
237330
|
+
}
|
|
237331
|
+
function stepsOf(job) {
|
|
237332
|
+
if (!job || typeof job !== "object") return [];
|
|
237333
|
+
return Array.isArray(job.steps) ? job.steps : [];
|
|
237334
|
+
}
|
|
237335
|
+
function inspectJob(job) {
|
|
237336
|
+
const out = {
|
|
237337
|
+
contractGate: false,
|
|
237338
|
+
deployGate: false,
|
|
237339
|
+
environment: jobEnvironmentName(job),
|
|
237340
|
+
disabled: isExplicitlyDisabled(job)
|
|
237341
|
+
};
|
|
237342
|
+
if (out.disabled) return out;
|
|
237343
|
+
for (const step of stepsOf(job)) {
|
|
237344
|
+
if (!step || typeof step !== "object") continue;
|
|
237345
|
+
if (isExplicitlyDisabled(step)) continue;
|
|
237346
|
+
if (step.uses != null && isContractGateUses(step.uses)) out.contractGate = true;
|
|
237347
|
+
if (step.run != null && isDeployGateInvocation(step.run, step.shell)) out.deployGate = true;
|
|
237348
|
+
}
|
|
237349
|
+
return out;
|
|
237350
|
+
}
|
|
237351
|
+
function inspectWorkflowYaml(body) {
|
|
237352
|
+
const empty = { contractGate: false, deployGate: false, deployEnvironments: [] };
|
|
237353
|
+
let doc;
|
|
237354
|
+
try {
|
|
237355
|
+
doc = yaml.load(String(body || ""));
|
|
237356
|
+
} catch {
|
|
237357
|
+
return empty;
|
|
237358
|
+
}
|
|
237359
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return empty;
|
|
237360
|
+
const jobs = doc.jobs && typeof doc.jobs === "object" && !Array.isArray(doc.jobs) ? doc.jobs : null;
|
|
237361
|
+
if (!jobs) return empty;
|
|
237362
|
+
let contractGate = false;
|
|
237363
|
+
let deployGate = false;
|
|
237364
|
+
const deployEnvironments = [];
|
|
237365
|
+
for (const name of Object.keys(jobs)) {
|
|
237366
|
+
const info = inspectJob(jobs[name]);
|
|
237367
|
+
if (info.contractGate) contractGate = true;
|
|
237368
|
+
if (info.deployGate) {
|
|
237369
|
+
deployGate = true;
|
|
237370
|
+
if (info.environment) deployEnvironments.push(info.environment);
|
|
237371
|
+
}
|
|
237372
|
+
}
|
|
237373
|
+
return { contractGate, deployGate, deployEnvironments };
|
|
237374
|
+
}
|
|
237375
|
+
module2.exports = {
|
|
237376
|
+
CONTRACT_GATE_ACTION,
|
|
237377
|
+
isPinnedMajor,
|
|
237378
|
+
parseActionUses,
|
|
237379
|
+
isContractGateUses,
|
|
237380
|
+
isExplicitlyDisabled,
|
|
237381
|
+
jobEnvironmentName,
|
|
237382
|
+
isDeployGateInvocation,
|
|
237383
|
+
inspectJob,
|
|
237384
|
+
inspectWorkflowYaml
|
|
237385
|
+
};
|
|
237386
|
+
}
|
|
237387
|
+
});
|
|
237388
|
+
|
|
237015
237389
|
// src/provider/github-enforcement.js
|
|
237016
237390
|
var require_github_enforcement = __commonJS({
|
|
237017
237391
|
"src/provider/github-enforcement.js"(exports2, module2) {
|
|
@@ -237023,7 +237397,107 @@ var require_github_enforcement = __commonJS({
|
|
|
237023
237397
|
extractRequiredContexts
|
|
237024
237398
|
} = require_setup_required_check();
|
|
237025
237399
|
var { GATE_ACTION } = require_contract_gate_workflow();
|
|
237026
|
-
var
|
|
237400
|
+
var { inspectWorkflowYaml } = require_workflow_scan();
|
|
237401
|
+
var EVIDENCE_SPEC = "provider-enforcement-evidence.v2";
|
|
237402
|
+
var CODERIFTS_GITHUB_APP_SLUG = "coderifts";
|
|
237403
|
+
var CODERIFTS_GITHUB_APP_ID = 2860592;
|
|
237404
|
+
var REASON = Object.freeze({
|
|
237405
|
+
ISSUER_BOUND: "issuer_bound",
|
|
237406
|
+
NO_ISSUER_BINDING: "no_issuer_binding",
|
|
237407
|
+
LEGACY_CONTEXTS_NO_ISSUER_BINDING: "legacy_contexts_no_issuer_binding",
|
|
237408
|
+
ISSUER_APP_ID_MISMATCH: "issuer_app_id_mismatch",
|
|
237409
|
+
ISSUER_APP_ID_UNRESOLVABLE: "issuer_app_id_unresolvable",
|
|
237410
|
+
REQUIRED_CHECK_ABSENT: "required_check_absent",
|
|
237411
|
+
ACTIVE_JOB: "active_job",
|
|
237412
|
+
NO_ACTIVE_JOB: "no_active_job",
|
|
237413
|
+
ENVIRONMENT_CORRELATED: "environment_correlated",
|
|
237414
|
+
ENVIRONMENT_MISMATCH: "environment_mismatch"
|
|
237415
|
+
});
|
|
237416
|
+
function verifiedRequiresBinding(layer2) {
|
|
237417
|
+
if (!layer2 || layer2.status !== STATUS.VERIFIED) return true;
|
|
237418
|
+
if (layer2.id === "required_check") return layer2.reason_code === REASON.ISSUER_BOUND;
|
|
237419
|
+
if (layer2.id === "workflow_contract_gate" || layer2.id === "workflow_deploy_gate") {
|
|
237420
|
+
return layer2.reason_code === REASON.ACTIVE_JOB;
|
|
237421
|
+
}
|
|
237422
|
+
if (layer2.id === "environment_protection") {
|
|
237423
|
+
return layer2.reason_code === REASON.ENVIRONMENT_CORRELATED;
|
|
237424
|
+
}
|
|
237425
|
+
return true;
|
|
237426
|
+
}
|
|
237427
|
+
function expectedIssuerAppId() {
|
|
237428
|
+
const id = Number(CODERIFTS_GITHUB_APP_ID);
|
|
237429
|
+
return Number.isInteger(id) && id > 0 ? id : null;
|
|
237430
|
+
}
|
|
237431
|
+
function ourCheckEntries(rsc) {
|
|
237432
|
+
if (!rsc || !Array.isArray(rsc.checks)) return [];
|
|
237433
|
+
return rsc.checks.filter((c) => c && typeof c.context === "string" && c.context === CHECK_NAME);
|
|
237434
|
+
}
|
|
237435
|
+
function numericAppId(value) {
|
|
237436
|
+
if (value == null || value === "") return null;
|
|
237437
|
+
const n = Number(value);
|
|
237438
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
237439
|
+
}
|
|
237440
|
+
function evaluateRequiredCheck(protection) {
|
|
237441
|
+
const expected = expectedIssuerAppId();
|
|
237442
|
+
const rsc = protection && protection.required_status_checks;
|
|
237443
|
+
const contexts = extractRequiredContexts(protection);
|
|
237444
|
+
if (expected == null) {
|
|
237445
|
+
return {
|
|
237446
|
+
status: STATUS.UNVERIFIABLE,
|
|
237447
|
+
reason_code: REASON.ISSUER_APP_ID_UNRESOLVABLE,
|
|
237448
|
+
reason: "CodeRifts GitHub App id could not be determined from public metadata; required_check is UNVERIFIABLE (never VERIFIED without an issuer id)",
|
|
237449
|
+
required_contexts: contexts,
|
|
237450
|
+
app_id: null
|
|
237451
|
+
};
|
|
237452
|
+
}
|
|
237453
|
+
const ours = ourCheckEntries(rsc);
|
|
237454
|
+
if (ours.length > 0) {
|
|
237455
|
+
const bound = ours.find((c) => numericAppId(c.app_id) === expected);
|
|
237456
|
+
if (bound) {
|
|
237457
|
+
return {
|
|
237458
|
+
status: STATUS.VERIFIED,
|
|
237459
|
+
reason_code: REASON.ISSUER_BOUND,
|
|
237460
|
+
reason: `required_status_checks.checks binds "${CHECK_NAME}" to GitHub App id ${expected} (slug ${CODERIFTS_GITHUB_APP_SLUG})`,
|
|
237461
|
+
required_contexts: contexts,
|
|
237462
|
+
app_id: expected
|
|
237463
|
+
};
|
|
237464
|
+
}
|
|
237465
|
+
const other = ours.find((c) => numericAppId(c.app_id) != null);
|
|
237466
|
+
if (other) {
|
|
237467
|
+
return {
|
|
237468
|
+
status: STATUS.NOT_VERIFIED,
|
|
237469
|
+
reason_code: REASON.ISSUER_APP_ID_MISMATCH,
|
|
237470
|
+
reason: `required_status_checks.checks binds "${CHECK_NAME}" to app_id ${numericAppId(other.app_id)}, not CodeRifts App id ${expected}`,
|
|
237471
|
+
required_contexts: contexts,
|
|
237472
|
+
app_id: numericAppId(other.app_id),
|
|
237473
|
+
expected_app_id: expected
|
|
237474
|
+
};
|
|
237475
|
+
}
|
|
237476
|
+
return {
|
|
237477
|
+
status: STATUS.NOT_VERIFIED,
|
|
237478
|
+
reason_code: REASON.NO_ISSUER_BINDING,
|
|
237479
|
+
reason: `required_status_checks.checks lists "${CHECK_NAME}" with no app_id (name-only; any repository writer can post a success status with that context)`,
|
|
237480
|
+
required_contexts: contexts,
|
|
237481
|
+
app_id: null
|
|
237482
|
+
};
|
|
237483
|
+
}
|
|
237484
|
+
if (contexts.includes(CHECK_NAME)) {
|
|
237485
|
+
return {
|
|
237486
|
+
status: STATUS.NOT_VERIFIED,
|
|
237487
|
+
reason_code: REASON.LEGACY_CONTEXTS_NO_ISSUER_BINDING,
|
|
237488
|
+
reason: `required_status_checks.contexts lists "${CHECK_NAME}" but the legacy contexts array has no app_id at all (legacy_contexts_no_issuer_binding)`,
|
|
237489
|
+
required_contexts: contexts,
|
|
237490
|
+
app_id: null
|
|
237491
|
+
};
|
|
237492
|
+
}
|
|
237493
|
+
return {
|
|
237494
|
+
status: STATUS.NOT_VERIFIED,
|
|
237495
|
+
reason_code: REASON.REQUIRED_CHECK_ABSENT,
|
|
237496
|
+
reason: `required_status_checks does not include "${CHECK_NAME}" (have: ${contexts.join(", ") || "none"})`,
|
|
237497
|
+
required_contexts: contexts,
|
|
237498
|
+
app_id: null
|
|
237499
|
+
};
|
|
237500
|
+
}
|
|
237027
237501
|
var APP_INSTALLATION = Object.freeze({
|
|
237028
237502
|
can_query_protection: false,
|
|
237029
237503
|
permissions: Object.freeze([
|
|
@@ -237189,12 +237663,14 @@ var require_github_enforcement = __commonJS({
|
|
|
237189
237663
|
} else {
|
|
237190
237664
|
const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
|
|
237191
237665
|
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237192
|
-
const
|
|
237193
|
-
|
|
237194
|
-
|
|
237195
|
-
|
|
237666
|
+
const required = evaluateRequiredCheck(protection);
|
|
237667
|
+
layers.push(layer("required_check", required.status, {
|
|
237668
|
+
reason: required.reason,
|
|
237669
|
+
reason_code: required.reason_code,
|
|
237196
237670
|
endpoint,
|
|
237197
|
-
required_contexts:
|
|
237671
|
+
required_contexts: required.required_contexts,
|
|
237672
|
+
app_id: required.app_id,
|
|
237673
|
+
...required.expected_app_id != null ? { expected_app_id: required.expected_app_id } : {}
|
|
237198
237674
|
}));
|
|
237199
237675
|
const admins = enforceAdminsEnabled(protection);
|
|
237200
237676
|
layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
@@ -237246,24 +237722,55 @@ var require_github_enforcement = __commonJS({
|
|
|
237246
237722
|
}));
|
|
237247
237723
|
} else {
|
|
237248
237724
|
const ok = environmentProtected(environmentRead.body);
|
|
237249
|
-
|
|
237250
|
-
|
|
237251
|
-
|
|
237252
|
-
|
|
237253
|
-
|
|
237725
|
+
const referenced = (localFiles.deployGateEnvironments || []).map(String);
|
|
237726
|
+
const correlated = ok && environmentName && referenced.includes(environmentName);
|
|
237727
|
+
if (correlated) {
|
|
237728
|
+
layers.push(layer("environment_protection", STATUS.VERIFIED, {
|
|
237729
|
+
reason: `GitHub Environment "${environmentName}" has protection rules and is referenced by an active deploy-gate job (environment:)`,
|
|
237730
|
+
reason_code: REASON.ENVIRONMENT_CORRELATED,
|
|
237731
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237732
|
+
environment: environmentName,
|
|
237733
|
+
workflow_environments: referenced
|
|
237734
|
+
}));
|
|
237735
|
+
} else if (!ok) {
|
|
237736
|
+
layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
|
|
237737
|
+
reason: `GitHub Environment "${environmentName}" exists but has no protection_rules and no deployment_branch_policy`,
|
|
237738
|
+
reason_code: REASON.ENVIRONMENT_MISMATCH,
|
|
237739
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237740
|
+
environment: environmentName,
|
|
237741
|
+
workflow_environments: referenced
|
|
237742
|
+
}));
|
|
237743
|
+
} else {
|
|
237744
|
+
layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
|
|
237745
|
+
reason: `GitHub Environment "${environmentName}" is protected but no active deploy-gate job sets environment: ${environmentName} (have: ${referenced.join(", ") || "none"})`,
|
|
237746
|
+
reason_code: REASON.ENVIRONMENT_MISMATCH,
|
|
237747
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237748
|
+
environment: environmentName,
|
|
237749
|
+
workflow_environments: referenced
|
|
237750
|
+
}));
|
|
237751
|
+
}
|
|
237254
237752
|
}
|
|
237255
237753
|
const cg = localFiles.contractGateFiles || [];
|
|
237256
237754
|
layers.push(layer("workflow_contract_gate", cg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237257
|
-
reason: cg.length ? `
|
|
237755
|
+
reason: cg.length ? `active job step uses ${GATE_ACTION} (${cg.join(", ")})` : `no active .github/workflows job uses ${GATE_ACTION} (comments, disabled jobs, and string literals do not count)`,
|
|
237756
|
+
reason_code: cg.length ? REASON.ACTIVE_JOB : REASON.NO_ACTIVE_JOB,
|
|
237258
237757
|
endpoint: "local:.github/workflows",
|
|
237259
237758
|
files: cg
|
|
237260
237759
|
}));
|
|
237261
237760
|
const dg = localFiles.deployGateFiles || [];
|
|
237262
237761
|
layers.push(layer("workflow_deploy_gate", dg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237263
|
-
reason: dg.length ? `
|
|
237762
|
+
reason: dg.length ? `active job step invokes coderifts deploy-gate (${dg.join(", ")})` : "no active .github/workflows job invokes coderifts deploy-gate (comments, disabled jobs, and string literals do not count)",
|
|
237763
|
+
reason_code: dg.length ? REASON.ACTIVE_JOB : REASON.NO_ACTIVE_JOB,
|
|
237264
237764
|
endpoint: "local:.github/workflows",
|
|
237265
237765
|
files: dg
|
|
237266
237766
|
}));
|
|
237767
|
+
for (const l of layers) {
|
|
237768
|
+
if (l.status === STATUS.VERIFIED && !verifiedRequiresBinding(l)) {
|
|
237769
|
+
l.status = STATUS.NOT_VERIFIED;
|
|
237770
|
+
l.reason_code = l.reason_code || REASON.NO_ISSUER_BINDING;
|
|
237771
|
+
l.reason = `${l.reason || l.id} \u2014 downgraded: VERIFIED requires issuer or active-job binding`;
|
|
237772
|
+
}
|
|
237773
|
+
}
|
|
237267
237774
|
const byId = Object.fromEntries(layers.map((l) => [l.id, l]));
|
|
237268
237775
|
const inescapable = LAYER_IDS.every((id) => byId[id] && byId[id].status === STATUS.VERIFIED);
|
|
237269
237776
|
return {
|
|
@@ -237271,7 +237778,7 @@ var require_github_enforcement = __commonJS({
|
|
|
237271
237778
|
inescapable_deploy: inescapable,
|
|
237272
237779
|
claim: {
|
|
237273
237780
|
inescapable_deploy: inescapable,
|
|
237274
|
-
basis: inescapable ? "all six layers VERIFIED
|
|
237781
|
+
basis: inescapable ? "all six layers VERIFIED (issuer-bound required check + active workflow jobs + correlated environment)" : "inescapable_deploy stays false until every layer is VERIFIED"
|
|
237275
237782
|
}
|
|
237276
237783
|
};
|
|
237277
237784
|
}
|
|
@@ -237282,12 +237789,13 @@ var require_github_enforcement = __commonJS({
|
|
|
237282
237789
|
const dir = path.join(cwd, ".github", "workflows");
|
|
237283
237790
|
const contractGateFiles = [];
|
|
237284
237791
|
const deployGateFiles = [];
|
|
237285
|
-
|
|
237792
|
+
const deployGateEnvironments = [];
|
|
237793
|
+
if (!exists(dir)) return { contractGateFiles, deployGateFiles, deployGateEnvironments };
|
|
237286
237794
|
let names = [];
|
|
237287
237795
|
try {
|
|
237288
237796
|
names = readdir(dir);
|
|
237289
237797
|
} catch {
|
|
237290
|
-
return { contractGateFiles, deployGateFiles };
|
|
237798
|
+
return { contractGateFiles, deployGateFiles, deployGateEnvironments };
|
|
237291
237799
|
}
|
|
237292
237800
|
for (const name of names) {
|
|
237293
237801
|
if (!/\.ya?ml$/i.test(name)) continue;
|
|
@@ -237298,10 +237806,18 @@ var require_github_enforcement = __commonJS({
|
|
|
237298
237806
|
} catch {
|
|
237299
237807
|
continue;
|
|
237300
237808
|
}
|
|
237301
|
-
|
|
237302
|
-
if (
|
|
237809
|
+
const evidence = inspectWorkflowYaml(body);
|
|
237810
|
+
if (evidence.contractGate) contractGateFiles.push(rel);
|
|
237811
|
+
if (evidence.deployGate) {
|
|
237812
|
+
deployGateFiles.push(rel);
|
|
237813
|
+
for (const envName of evidence.deployEnvironments) {
|
|
237814
|
+
if (envName && !deployGateEnvironments.includes(envName)) {
|
|
237815
|
+
deployGateEnvironments.push(envName);
|
|
237816
|
+
}
|
|
237817
|
+
}
|
|
237818
|
+
}
|
|
237303
237819
|
}
|
|
237304
|
-
return { contractGateFiles, deployGateFiles };
|
|
237820
|
+
return { contractGateFiles, deployGateFiles, deployGateEnvironments };
|
|
237305
237821
|
}
|
|
237306
237822
|
async function queryGitHubEnforcement({
|
|
237307
237823
|
owner,
|
|
@@ -237336,12 +237852,17 @@ var require_github_enforcement = __commonJS({
|
|
|
237336
237852
|
NO_TOKEN_INSTRUCTION,
|
|
237337
237853
|
LAYER_IDS,
|
|
237338
237854
|
STATUS,
|
|
237855
|
+
REASON,
|
|
237339
237856
|
CHECK_NAME,
|
|
237340
237857
|
GATE_ACTION,
|
|
237858
|
+
CODERIFTS_GITHUB_APP_ID,
|
|
237859
|
+
CODERIFTS_GITHUB_APP_SLUG,
|
|
237341
237860
|
resolveGitHubAuth,
|
|
237342
237861
|
redactSecrets,
|
|
237343
237862
|
createGitHubHttpClient,
|
|
237344
237863
|
evaluateLayers,
|
|
237864
|
+
evaluateRequiredCheck,
|
|
237865
|
+
verifiedRequiresBinding,
|
|
237345
237866
|
scanLocalWorkflows,
|
|
237346
237867
|
queryGitHubEnforcement,
|
|
237347
237868
|
enforceAdminsEnabled,
|
|
@@ -237417,7 +237938,8 @@ var require_enforce_check = __commonJS({
|
|
|
237417
237938
|
const missing = report.layers.filter((l) => l.status !== "VERIFIED");
|
|
237418
237939
|
for (const l of missing) {
|
|
237419
237940
|
if (l.id === "required_check") {
|
|
237420
|
-
lines.push(chalk.dim(' require "CodeRifts / contract-gate"
|
|
237941
|
+
lines.push(chalk.dim(' require "CodeRifts / contract-gate" bound to the CodeRifts GitHub App (app_id)'));
|
|
237942
|
+
lines.push(chalk.dim(" on default-branch protection \u2014 name-only and legacy contexts[] are NOT_VERIFIED"));
|
|
237421
237943
|
lines.push(chalk.dim(" (coderifts setup-required-check --apply)"));
|
|
237422
237944
|
} else if (l.id === "enforce_admins") {
|
|
237423
237945
|
lines.push(chalk.dim(" set enforce_admins: true so administrators cannot bypass"));
|
|
@@ -237425,12 +237947,13 @@ var require_enforce_check = __commonJS({
|
|
|
237425
237947
|
} else if (l.id === "strict_status_checks") {
|
|
237426
237948
|
lines.push(chalk.dim(" set required_status_checks.strict: true"));
|
|
237427
237949
|
} else if (l.id === "environment_protection") {
|
|
237428
|
-
lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name
|
|
237950
|
+
lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name>,"));
|
|
237951
|
+
lines.push(chalk.dim(" and set that same name on the deploy-gate job's environment: key"));
|
|
237429
237952
|
} else if (l.id === "workflow_contract_gate") {
|
|
237430
|
-
lines.push(chalk.dim(" add
|
|
237431
|
-
lines.push(chalk.dim(" (coderifts init --agents)"));
|
|
237953
|
+
lines.push(chalk.dim(" add an active job step: uses: coderifts/contract-gate@v0"));
|
|
237954
|
+
lines.push(chalk.dim(" (comments / if: false / wrong org do not count; coderifts init --agents)"));
|
|
237432
237955
|
} else if (l.id === "workflow_deploy_gate") {
|
|
237433
|
-
lines.push(chalk.dim(" add
|
|
237956
|
+
lines.push(chalk.dim(" add an active CD step that invokes: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>"));
|
|
237434
237957
|
}
|
|
237435
237958
|
}
|
|
237436
237959
|
}
|
|
@@ -238767,7 +239290,7 @@ var require_outcome = __commonJS({
|
|
|
238767
239290
|
var require_cursor_hook = __commonJS({
|
|
238768
239291
|
"src/commands/cursor-hook.js"(exports2, module2) {
|
|
238769
239292
|
"use strict";
|
|
238770
|
-
var { runClaudeHook } = require_claude_hook();
|
|
239293
|
+
var { runClaudeHook, pickToolField, filePathFromInput, contentFromInput, coerceToolInput } = require_claude_hook();
|
|
238771
239294
|
var USAGE = `Usage: coderifts cursor-hook
|
|
238772
239295
|
|
|
238773
239296
|
Cursor preToolUse adapter. Reads Cursor's hook JSON on stdin, emits
|
|
@@ -238794,15 +239317,15 @@ Unparseable stdin denies (fail-closed).
|
|
|
238794
239317
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
238795
239318
|
return { ok: false, reason: "stdin JSON is not an object" };
|
|
238796
239319
|
}
|
|
238797
|
-
const toolName =
|
|
238798
|
-
if (!toolName) return { ok: false, reason: "missing tool_name" };
|
|
238799
|
-
const toolInput = obj
|
|
239320
|
+
const toolName = pickToolField(obj, "tool_name", "toolName", "name");
|
|
239321
|
+
if (typeof toolName !== "string" || !toolName) return { ok: false, reason: "missing tool_name" };
|
|
239322
|
+
const toolInput = coerceToolInput(pickToolField(obj, "tool_input", "toolInput", "input")) || (filePathFromInput(obj) ? obj : null);
|
|
238800
239323
|
if (!toolInput) return { ok: false, reason: "missing tool_input" };
|
|
238801
239324
|
return { ok: true, payload: obj, toolName, toolInput };
|
|
238802
239325
|
}
|
|
238803
239326
|
function toClaudeShape(toolName, toolInput) {
|
|
238804
|
-
const filePath = toolInput
|
|
238805
|
-
const content = toolInput
|
|
239327
|
+
const filePath = filePathFromInput(toolInput) || null;
|
|
239328
|
+
const content = contentFromInput(toolInput);
|
|
238806
239329
|
const out = { ...toolInput };
|
|
238807
239330
|
if (filePath) out.file_path = filePath;
|
|
238808
239331
|
if (content !== void 0) out.content = content;
|
|
@@ -240564,7 +241087,7 @@ program.command("registry-gate [dir]").description("Admit a directory of OpenAPI
|
|
|
240564
241087
|
process.exitCode = code;
|
|
240565
241088
|
process.exit(code);
|
|
240566
241089
|
});
|
|
240567
|
-
program.command("init [template]").description("Generate a .coderifts.yml from a policy template, or wire governed agent work (--agents)").option("--agents", "Wire this repo for governed agent work (MCP config, rule files, host hook, CI gate)").option("--hosts <list>", "With --agents: claude, cursor, or all (default: all)", "all").option("--no-hook", "With --agents: skip host hook install").option("--no-workflow", "With --agents: skip writing .github/workflows/coderifts.yml").option("--dry-run", "With --agents: print the plan without writing").option("--check", "With --agents: report which of the four pieces are present (no write)").option("--out <dir>", "With --agents: target directory (default: cwd)").action(async (template, options) => {
|
|
241090
|
+
program.command("init [template]").description("Generate a .coderifts.yml from a policy template, or wire governed agent work (--agents)").option("--agents", "Wire this repo for governed agent work (MCP config, rule files, host hook, CI gate)").option("--hosts <list>", "With --agents: claude, cursor, copilot, or all (default: all)", "all").option("--no-hook", "With --agents: skip host hook install").option("--no-workflow", "With --agents: skip writing .github/workflows/coderifts.yml").option("--dry-run", "With --agents: print the plan without writing").option("--check", "With --agents: report which of the four pieces are present (no write)").option("--out <dir>", "With --agents: target directory (default: cwd)").action(async (template, options) => {
|
|
240568
241091
|
const {
|
|
240569
241092
|
runInitAgents,
|
|
240570
241093
|
agentFlagsRequireAgents,
|