coderifts 8.1.0 → 8.3.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 +12 -0
- package/bin/coderifts.js +13 -0
- package/dist/.build-source-sha +2 -2
- package/dist/cli.js +312 -6
- package/package.json +2 -2
- package/scripts/assert-guard-major.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 8.3.0 - 2026-09-05
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- `@coderifts/agent-guard` range raised to `^17.1.0`. Guard 17.1.0 fixes the audience binding (the Guard now sends top-level `audience`, which the server actually reads) and makes its public suite hermetic. No CLI behaviour change beyond the installed guard minor.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## 8.2.0 - 2026-09-05
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `@coderifts/agent-guard` range raised to `^17.0.0` (MIN_GUARD_MAJOR 17). Guard 17 blocks a raw shell carrying a mutation by default (a host-declared mutating_shell now reaches the UNGUARDED_MUTATION decision instead of slipping through). The CLI range must match so `npm i -g coderifts` installs the guard whose default the docs describe. No CLI behaviour change beyond the installed guard major.
|
|
13
|
+
|
|
14
|
+
|
|
3
15
|
## 8.1.0 - 2026-09-05
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/bin/coderifts.js
CHANGED
|
@@ -52,6 +52,19 @@ program
|
|
|
52
52
|
// Exit status is set only at this command boundary (library never process.exit).
|
|
53
53
|
// program.parse() does not await async actions; process.exit here makes the gate's
|
|
54
54
|
// fail-closed status authoritative for npm prepublishOnly / child_process callers.
|
|
55
|
+
// ── doctor (1222) — the adoption entry point ──
|
|
56
|
+
// Tier 0 needs no key and calls no model; Tier 1 is BYOK on the developer's own machine.
|
|
57
|
+
program
|
|
58
|
+
.command('doctor')
|
|
59
|
+
.description('Would an agent select CodeRifts for a contract change? Structural check (no key), or the 15-fixture benchmark on your own model.')
|
|
60
|
+
.option('--tools <file>', 'Your host\'s tools/list response (array, { tools }, or { result: { tools } })')
|
|
61
|
+
.option('--model <id>', 'TIER 1: run the fixtures through this model using YOUR key from the environment')
|
|
62
|
+
.option('--mock', 'TIER 1 dry run: a deterministic scripted model. Measures the script, not a model.')
|
|
63
|
+
.action(async (options) => {
|
|
64
|
+
const { runDoctor } = require('../src/commands/doctor');
|
|
65
|
+
await runDoctor(options);
|
|
66
|
+
});
|
|
67
|
+
|
|
55
68
|
program
|
|
56
69
|
.command('publish-gate')
|
|
57
70
|
.description('Gate npm publish on contract-artifact preflight (before=git baseline, after=working tree)')
|
package/dist/.build-source-sha
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "GENERATED by scripts/build.js. sha256 over src/**.js + bin/**.js (path + bytes). test/build-freshness.test.js recomputes it; a mismatch means dist/cli.js is stale.",
|
|
3
|
-
"sha256": "
|
|
4
|
-
"file_count":
|
|
3
|
+
"sha256": "9596f98c3c463ea53896621305484fd1ca3c5685b15c410fa4205c07a285c709",
|
|
4
|
+
"file_count": 44
|
|
5
5
|
}
|
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: "8.
|
|
3031
|
+
version: "8.3.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",
|
|
@@ -3074,7 +3074,7 @@ var require_package = __commonJS({
|
|
|
3074
3074
|
test: "node --test test/*.test.js"
|
|
3075
3075
|
},
|
|
3076
3076
|
dependencies: {
|
|
3077
|
-
"@coderifts/agent-guard": "^
|
|
3077
|
+
"@coderifts/agent-guard": "^17.1.0",
|
|
3078
3078
|
chalk: "^4.1.2",
|
|
3079
3079
|
"cli-table3": "^0.6.4",
|
|
3080
3080
|
commander: "^12.0.0",
|
|
@@ -66622,10 +66622,14 @@ var require_unguarded_mutation = __commonJS({
|
|
|
66622
66622
|
"use strict";
|
|
66623
66623
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
66624
66624
|
exports2.ADVISORY_ENV_VAR = void 0;
|
|
66625
|
+
exports2.isDeclaredMutatingClass = isDeclaredMutatingClass;
|
|
66625
66626
|
exports2.decideUnguardedMutation = decideUnguardedMutation;
|
|
66626
66627
|
var ADVISORY_TRUE = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
66627
66628
|
exports2.ADVISORY_ENV_VAR = "CODERIFTS_ADVISORY";
|
|
66628
66629
|
var RISK = 'A mutating tool call reached the guard with no verified receipt: the detector did not recognise a contract artifact, so nothing was preflighted and nothing was signed. "Not a contract change" describes the artifact, not the action \u2014 the write still happens.';
|
|
66630
|
+
function isDeclaredMutatingClass(cls) {
|
|
66631
|
+
return typeof cls === "string" && cls.startsWith("mutating");
|
|
66632
|
+
}
|
|
66629
66633
|
function decideUnguardedMutation(mutating, policy, env) {
|
|
66630
66634
|
if (!mutating)
|
|
66631
66635
|
return { stop: false };
|
|
@@ -71324,7 +71328,21 @@ These feed DIFFERENT halves of the system \u2014 the top level binds the ATOMIC
|
|
|
71324
71328
|
["target_uri", "targetUri"],
|
|
71325
71329
|
["tenant_id", "tenantId"],
|
|
71326
71330
|
["policy_hash", "policyHash"],
|
|
71327
|
-
|
|
71331
|
+
// 1402 — `audience_hash` IS NOT ON THIS LIST, and its absence is the fix.
|
|
71332
|
+
//
|
|
71333
|
+
// MEASURED 2026-09-06 against the server, read-only:
|
|
71334
|
+
// src/change-set.js:1138 reads TOP-LEVEL `input.audience`, strict /^v:[0-9a-f]{12}$/
|
|
71335
|
+
// execution-grant-v2.js:166 derives `audience_hash: sha256pref(audience)` ITSELF
|
|
71336
|
+
// execution-grant-request.v2.producer.json states it outright: "`audience_hash` is NOT a
|
|
71337
|
+
// request field of its own … sending it separately has no
|
|
71338
|
+
// effect and it stays out of this schema"
|
|
71339
|
+
//
|
|
71340
|
+
// So the Guard was sending a field nobody reads. Worse, it was ALSO sending the audience under
|
|
71341
|
+
// `context.audience`, which the handler does not read either (it reads `input.audience`) — both
|
|
71342
|
+
// channels were severed, and the binding a host configured reached the server through neither.
|
|
71343
|
+
// A field that looks bound and is not is the exact failure the server's own 1206 note describes.
|
|
71344
|
+
//
|
|
71345
|
+
// The fix is to send what the server consumes: top-level `audience`. See guard.ts.
|
|
71328
71346
|
["expected_state_token", "expectedStateToken"]
|
|
71329
71347
|
]);
|
|
71330
71348
|
function v2WireFields(config) {
|
|
@@ -71819,7 +71837,8 @@ var require_guard = __commonJS({
|
|
|
71819
71837
|
const suppressedByStrict = config.requireExplicitArtifacts === true && redacted.nonContract === true && (!detection.artifacts || detection.artifacts.length === 0) && detection.confident && !detection.trigger;
|
|
71820
71838
|
if (!detection.trigger || suppressedByStrict) {
|
|
71821
71839
|
emit(config, { type: "detection_skip", at: iso(), signals: detection.signals, detectorVersion: detector.version });
|
|
71822
|
-
const
|
|
71840
|
+
const declaredMutating = (0, unguarded_mutation_js_1.isDeclaredMutatingClass)(redacted.mutationClass);
|
|
71841
|
+
const um = (0, unguarded_mutation_js_1.decideUnguardedMutation)((0, freshness_js_1.isMutatingCall)(redacted) || declaredMutating, config.requireGuardedMutation, process.env);
|
|
71823
71842
|
if (um.stop) {
|
|
71824
71843
|
emit(config, { type: "unguarded_mutation_blocked", at: iso(), cause: um.detail });
|
|
71825
71844
|
breakerRecord(config);
|
|
@@ -71867,6 +71886,17 @@ var require_guard = __commonJS({
|
|
|
71867
71886
|
previous_receipt: resolvePreviousReceipt(config),
|
|
71868
71887
|
idempotency_key: void 0
|
|
71869
71888
|
};
|
|
71889
|
+
if (typeof config.audience === "string" && config.audience.length > 0) {
|
|
71890
|
+
if (/^v:[0-9a-f]{12}$/.test(config.audience)) {
|
|
71891
|
+
request.audience = config.audience;
|
|
71892
|
+
} else {
|
|
71893
|
+
emit(config, {
|
|
71894
|
+
type: "audience_not_bindable",
|
|
71895
|
+
at: iso(),
|
|
71896
|
+
cause: `audience ${JSON.stringify(config.audience)} is not the measured v:<12 hex> form; the server discards any other string to null, so it would NOT be bound. Nothing was sent for it.`
|
|
71897
|
+
});
|
|
71898
|
+
}
|
|
71899
|
+
}
|
|
71870
71900
|
let grantObs;
|
|
71871
71901
|
let grantForCall = null;
|
|
71872
71902
|
if ((0, execution_grant_js_1.isExecutionGrantEnabled)(config)) {
|
|
@@ -74423,7 +74453,10 @@ var require_tool_registry = __commonJS({
|
|
|
74423
74453
|
meta: tool.meta,
|
|
74424
74454
|
execute: async (args) => {
|
|
74425
74455
|
const deriveCfg = (0, auto_derive_js_1.normalizeAutoDerive)(guardCfg.autoDerive);
|
|
74426
|
-
const rebindRaw = () =>
|
|
74456
|
+
const rebindRaw = () => {
|
|
74457
|
+
const c = binder(tool, args, cls);
|
|
74458
|
+
return c && c.mutationClass === void 0 ? { ...c, mutationClass: cls } : c;
|
|
74459
|
+
};
|
|
74427
74460
|
let lastDerivation = null;
|
|
74428
74461
|
const rebind = async () => {
|
|
74429
74462
|
const c = rebindRaw();
|
|
@@ -77491,7 +77524,7 @@ var require_cjs4 = __commonJS({
|
|
|
77491
77524
|
exports2.createDbVersionToken = exports2.API_ABSENT_TOKEN = exports2.API_VERSION_TOKEN_PREFIX = exports2.apiTokenRaw = exports2.writeApiIfUnchanged = exports2.createApiVersionToken = exports2.identitiesEqual = exports2.fsObjectIdentity = exports2.UnsafeCasPathError = exports2.assertSafeCasPath = exports2.FS_ABSENT_TOKEN = exports2.FS_VERSION_TOKEN_PREFIX = exports2.fsTokenContentHash = exports2.createFsPriorContentResolver = exports2.writeFileIfUnchanged = exports2.readVersionedFile = exports2.createFsVersionToken = exports2.StaleVersionTokenAbort = exports2.executeIfUnchanged = exports2.RESIDUAL_UNCONDITIONAL_WRITE = exports2.conditionalWriteResidual = exports2.tokensEqual = exports2.buildConditionalWriteBasis = exports2.buildFreshnessBasis = exports2.collectFreshnessCallContext = exports2.artifactIdsForResolve = exports2.isMutatingCall = exports2.isWriteStyleCall = exports2.freshnessAllowsEnforce = exports2.computePathSetTreeHash = exports2.contentByteIdentical = exports2.assessWriteStylePrior = exports2.assessFreshness = exports2.deriveProofBanner = exports2.attachProofToAgentResponse = exports2.renderFinalAnswerProof = exports2.EXECUTION_PROOF_SPEC = exports2.assertEnforcedReceiptInvariant = exports2.hashExecutionResult = exports2.buildExecutionProof = exports2.COVERAGE_ATTEST_ENVELOPE_TAG = exports2.COVERAGE_ATTEST_SIGNING_PREFIX = exports2.COVERAGE_ATTEST_VERSION = exports2.coverageAttestSigningInput = exports2.kidFromCoverageAttestation = exports2.tryIssueCoverageAttestation = exports2.MONITOR_ATTEST_ENVELOPE_TAG = exports2.MONITOR_ATTEST_SIGNING_PREFIX = exports2.MONITOR_ATTEST_VERSION = exports2.receiptDigestOfToken = void 0;
|
|
77492
77525
|
exports2.AUTO_DERIVE_READ_TIMEOUT_MS = exports2.AUTO_DERIVE_SOURCE = exports2.defaultFsReader = exports2.normalizeAutoDerive = exports2.runAutoDerive = exports2.AUTO_RECHECK_MAX_CAP = exports2.clampMaxAttempts = exports2.normalizeAutoRecheck = exports2.runAutoRecheckLoop = exports2.coverageReport = exports2.DEPLOY_REPAIRABLE_REASONS = exports2.bindDeploy = exports2.NEXT_STEP_NOTE = exports2.NEXT_AGENT_ACTIONS = exports2.readNextAgentStep = exports2.verifyDeployReceiptToken = exports2.DEPLOY_RECEIPT_VIEW_SPEC = exports2.isVerifiedDeployReceiptView = exports2.asVerifiedDeployReceiptView = exports2.deployGate = exports2.gateDecision = exports2.RegistryConstructionError = exports2.guardToolRegistry = exports2.globToRegExp = exports2.matchGlob = exports2.blobMapKey = exports2.classifyByName = exports2.resolveArtifacts = exports2.REMEDIATION_LOOP_ATTESTATION_SPEC = exports2.isCasAttestation = exports2.readPriorBlockRemediation = exports2.readRemediationTransaction = exports2.buildRemediationLoopAttestation = exports2.COMMIT_EVIDENCE_MISSING = exports2.CAS_ATTESTATION_SPEC = exports2.strictCommitObservation = exports2.extractExecutorAttestationToken = exports2.evaluateCasEvidence = exports2.isExecuteIfUnchangedOutcome = exports2.isGuardExecutionProof = exports2.buildCasAttestation = exports2.REGISTRY_ABSENT_TOKEN = exports2.REGISTRY_VERSION_TOKEN_PREFIX = exports2.registryTokenRaw = exports2.writeRegistryIfUnchanged = exports2.createRegistryVersionToken = exports2.DB_ABSENT_TOKEN = exports2.DB_VERSION_TOKEN_PREFIX = exports2.dbTokenRaw = exports2.writeDbIfUnchanged = void 0;
|
|
77493
77526
|
exports2.inferFsPathFromArgs = exports2.FRESHNESS_RESOLVER_FIX = exports2.freshnessRefusalTeaching = exports2.formatGateRefusalBody = exports2.defaultSerializeLangGraphToolResult = exports2.bindLangGraphGuardOutcome = exports2.LangGraphToolsNotStructuredError = exports2.isLangGraphReactAgentTool = exports2.bindLangGraphTools = exports2.protectedToolToLangGraph = exports2.toLangGraphTools = exports2.langGraphToolAdapter = exports2.withCodeRiftsLangGraph = exports2.defaultSerializeAnthropicToolResult = exports2.bindAnthropicGuardOutcome = exports2.protectedToolToAnthropic = exports2.toAnthropicTools = exports2.anthropicToolAdapter = exports2.withCodeRiftsAnthropic = exports2.defaultSerializeOpenAIToolResult = exports2.bindOpenAIGuardOutcome = exports2.protectedToolToOpenAI = exports2.toOpenAITools = exports2.openAIToolAdapter = exports2.withCodeRiftsOpenAI = exports2.formatCoverageObservedLine = exports2.freezeCoverageObserved = exports2.createCoverageObserver = exports2.readExecutionGrantToken = exports2.isExecutionGrantEnabled = exports2.POSTURE_CLOCK_SKEW_TOLERANCE_MS = exports2.POSTURE_RECEIPT_V = exports2.verifyPostureReceipt = exports2.ATOMIC_V2_MAX_AGE_MS_CAP = exports2.CREDENTIAL_BOUNDARY_BARE_REJECTED = exports2.atomicConstructionProblems = exports2.isEnforcingAtomicV2 = exports2.PROFILE_ENFORCING_ATOMIC_V2 = exports2.isEnforcingAtomic = exports2.atomicOutcome = exports2.ATOMIC_PROFILE_UNSATISFIED = exports2.ATOMIC_INVARIANTS = exports2.createMutatorRegister = exports2._resetUnversionedProfileNoticeForTest = exports2.UNVERSIONED_PROFILE_NOTICE = exports2.PROFILE_ENFORCING_ATOMIC_V1 = exports2.PROFILE_ENFORCING_STRICT_V1 = exports2.guardedFractionAmongRoutes = exports2.foldTableSettledCalls = exports2.withCodeRifts = void 0;
|
|
77494
|
-
exports2.ADVISORY_ENV_VAR = exports2.decideUnguardedMutation = exports2.missingReceiptDecision = exports2.offlineReceiptVerifier = exports2.kidFromToolsetAttestation = exports2.tryIssueToolsetAttestation = exports2.toolsetAttestSigningInput = exports2.declarationEntriesFromTools = exports2.computeToolsetDigest = exports2.TOOLSET_ATTEST_STATEMENT = exports2.TOOLSET_ATTEST_ENVELOPE_TAG = exports2.TOOLSET_ATTEST_SIGNING_PREFIX = exports2.TOOLSET_ATTEST_VERSION = exports2.surfaceEnvelopeFields = exports2.isGuardOutcome = exports2.executeVercelToolCall = exports2.executeLangGraphToolCall = exports2.executeGeminiToolCall = exports2.executeAnthropicToolCall = exports2.executeOpenAIToolCall = exports2.executeProtectedTool = exports2.defaultSerializeVercelToolResult = exports2.bindVercelGuardOutcome = exports2.protectedToolToVercel = exports2.toVercelTools = exports2.vercelToolAdapter = exports2.withCodeRiftsVercel = exports2.defaultSerializeCrewAIToolResult = exports2.bindCrewAIToolOutcome = exports2.LANGCHAIN_ARTIFACT_SPEC = exports2.defaultSerializeLangChainToolResult = exports2.bindLangChainToolOutcome = exports2.defaultSerializeGeminiToolResult = exports2.bindGeminiGuardOutcome = exports2.protectedToolToFunctionDeclaration = exports2.toGeminiTools = exports2.geminiToolAdapter = exports2.withCodeRiftsGemini = exports2.measureFsAuthorizationToken = exports2.measureFsAuthorization = exports2.wrapWriteWithFsCas = exports2.inferFullFileWriteContent = void 0;
|
|
77527
|
+
exports2.ADVISORY_ENV_VAR = exports2.isDeclaredMutatingClass = exports2.decideUnguardedMutation = exports2.missingReceiptDecision = exports2.offlineReceiptVerifier = exports2.kidFromToolsetAttestation = exports2.tryIssueToolsetAttestation = exports2.toolsetAttestSigningInput = exports2.declarationEntriesFromTools = exports2.computeToolsetDigest = exports2.TOOLSET_ATTEST_STATEMENT = exports2.TOOLSET_ATTEST_ENVELOPE_TAG = exports2.TOOLSET_ATTEST_SIGNING_PREFIX = exports2.TOOLSET_ATTEST_VERSION = exports2.surfaceEnvelopeFields = exports2.isGuardOutcome = exports2.executeVercelToolCall = exports2.executeLangGraphToolCall = exports2.executeGeminiToolCall = exports2.executeAnthropicToolCall = exports2.executeOpenAIToolCall = exports2.executeProtectedTool = exports2.defaultSerializeVercelToolResult = exports2.bindVercelGuardOutcome = exports2.protectedToolToVercel = exports2.toVercelTools = exports2.vercelToolAdapter = exports2.withCodeRiftsVercel = exports2.defaultSerializeCrewAIToolResult = exports2.bindCrewAIToolOutcome = exports2.LANGCHAIN_ARTIFACT_SPEC = exports2.defaultSerializeLangChainToolResult = exports2.bindLangChainToolOutcome = exports2.defaultSerializeGeminiToolResult = exports2.bindGeminiGuardOutcome = exports2.protectedToolToFunctionDeclaration = exports2.toGeminiTools = exports2.geminiToolAdapter = exports2.withCodeRiftsGemini = exports2.measureFsAuthorizationToken = exports2.measureFsAuthorization = exports2.wrapWriteWithFsCas = exports2.inferFullFileWriteContent = void 0;
|
|
77495
77528
|
var guard_js_1 = require_guard();
|
|
77496
77529
|
Object.defineProperty(exports2, "guardToolCall", { enumerable: true, get: function() {
|
|
77497
77530
|
return guard_js_1.guardToolCall;
|
|
@@ -78267,6 +78300,9 @@ var require_cjs4 = __commonJS({
|
|
|
78267
78300
|
Object.defineProperty(exports2, "decideUnguardedMutation", { enumerable: true, get: function() {
|
|
78268
78301
|
return unguarded_mutation_js_1.decideUnguardedMutation;
|
|
78269
78302
|
} });
|
|
78303
|
+
Object.defineProperty(exports2, "isDeclaredMutatingClass", { enumerable: true, get: function() {
|
|
78304
|
+
return unguarded_mutation_js_1.isDeclaredMutatingClass;
|
|
78305
|
+
} });
|
|
78270
78306
|
Object.defineProperty(exports2, "ADVISORY_ENV_VAR", { enumerable: true, get: function() {
|
|
78271
78307
|
return unguarded_mutation_js_1.ADVISORY_ENV_VAR;
|
|
78272
78308
|
} });
|
|
@@ -230155,6 +230191,272 @@ var require_deploy_gate2 = __commonJS({
|
|
|
230155
230191
|
}
|
|
230156
230192
|
});
|
|
230157
230193
|
|
|
230194
|
+
// src/doctor.js
|
|
230195
|
+
var require_doctor = __commonJS({
|
|
230196
|
+
"src/doctor.js"(exports2, module2) {
|
|
230197
|
+
"use strict";
|
|
230198
|
+
var fs = require("fs");
|
|
230199
|
+
var path = require("path");
|
|
230200
|
+
function harness() {
|
|
230201
|
+
return require(path.join(__dirname, "..", "..", "..", "benchmark", "tool-selection-benchmark.js"));
|
|
230202
|
+
}
|
|
230203
|
+
var PREFLIGHT = "preflight_change_set";
|
|
230204
|
+
function bare(name) {
|
|
230205
|
+
return String(name == null ? "" : name).replace(/^coderifts\./, "");
|
|
230206
|
+
}
|
|
230207
|
+
function parseToolsDocument(doc) {
|
|
230208
|
+
if (Array.isArray(doc)) return { ok: true, tools: doc };
|
|
230209
|
+
if (doc && Array.isArray(doc.tools)) return { ok: true, tools: doc.tools };
|
|
230210
|
+
if (doc && doc.result && Array.isArray(doc.result.tools)) return { ok: true, tools: doc.result.tools };
|
|
230211
|
+
return {
|
|
230212
|
+
ok: false,
|
|
230213
|
+
why: "expected a tools/list response \u2014 an array, { tools: [...] }, or { result: { tools: [...] } }"
|
|
230214
|
+
};
|
|
230215
|
+
}
|
|
230216
|
+
function descriptionOf(t) {
|
|
230217
|
+
return typeof t.description === "string" ? t.description : "";
|
|
230218
|
+
}
|
|
230219
|
+
function schemaOf(t) {
|
|
230220
|
+
return t.inputSchema || t.input_schema || t.parameters || void 0;
|
|
230221
|
+
}
|
|
230222
|
+
function signalPreflightPresent(tools) {
|
|
230223
|
+
const hit = tools.find((t) => bare(t.name) === PREFLIGHT);
|
|
230224
|
+
return hit ? { ok: true, detail: `\`${bare(hit.name)}\` is in the table` } : {
|
|
230225
|
+
ok: false,
|
|
230226
|
+
detail: `no \`${PREFLIGHT}\` tool in the table \u2014 nothing to select for a contract change`,
|
|
230227
|
+
fix: "coderifts agent-setup"
|
|
230228
|
+
};
|
|
230229
|
+
}
|
|
230230
|
+
function signalDescriptionMatchesShipped(tools, shipped) {
|
|
230231
|
+
const hit = tools.find((t) => bare(t.name) === PREFLIGHT);
|
|
230232
|
+
if (!hit) return { ok: false, detail: "not applicable \u2014 no preflight tool", fix: "coderifts agent-setup" };
|
|
230233
|
+
const ship = shipped.find((t) => bare(t.name) === PREFLIGHT);
|
|
230234
|
+
if (!ship) return { ok: false, detail: "the shipped manifest carries no preflight tool to compare against" };
|
|
230235
|
+
const mine = descriptionOf(hit).trim();
|
|
230236
|
+
const theirs = descriptionOf(ship).trim();
|
|
230237
|
+
if (mine === theirs) return { ok: true, detail: "description is byte-identical to the shipped text" };
|
|
230238
|
+
const pct = theirs.length ? Math.round(mine.length / theirs.length * 100) : 0;
|
|
230239
|
+
return {
|
|
230240
|
+
ok: false,
|
|
230241
|
+
detail: `description DRIFTED from the shipped text (${mine.length} vs ${theirs.length} chars, ${pct}%) \u2014 the model reads this to decide, so a shortened or stale copy changes selection`,
|
|
230242
|
+
fix: "coderifts agent-setup --force"
|
|
230243
|
+
};
|
|
230244
|
+
}
|
|
230245
|
+
function signalSchemaValid(tools, h) {
|
|
230246
|
+
const checks = [];
|
|
230247
|
+
const attempt = (label, fn, arg) => {
|
|
230248
|
+
try {
|
|
230249
|
+
fn(arg);
|
|
230250
|
+
checks.push({ label, ok: true });
|
|
230251
|
+
} catch (err) {
|
|
230252
|
+
checks.push({ label, ok: false, why: err && err.message || "rejected" });
|
|
230253
|
+
}
|
|
230254
|
+
};
|
|
230255
|
+
const anthropicShape = tools.map((t) => ({ name: bare(t.name), description: descriptionOf(t), input_schema: schemaOf(t) }));
|
|
230256
|
+
if (typeof h.validateAnthropicTools === "function") attempt("anthropic", h.validateAnthropicTools, anthropicShape.map(h.toAnthropicTool || ((x) => x)));
|
|
230257
|
+
if (typeof h.validateOpenAITools === "function") attempt("openai", h.validateOpenAITools, anthropicShape.map(h.toOpenAITool || ((x) => x)));
|
|
230258
|
+
if (typeof h.validateGoogleTools === "function") attempt("google", h.validateGoogleTools, anthropicShape.map(h.toGoogleFunctionDeclaration || ((x) => x)));
|
|
230259
|
+
const bad = checks.filter((c) => !c.ok);
|
|
230260
|
+
return bad.length === 0 ? { ok: true, detail: `accepted by ${checks.map((c) => c.label).join(", ") || "(no validator available)"}` } : {
|
|
230261
|
+
ok: false,
|
|
230262
|
+
detail: `rejected by ${bad.map((c) => `${c.label} (${c.why})`).join("; ")}`,
|
|
230263
|
+
fix: "coderifts agent-setup --force"
|
|
230264
|
+
};
|
|
230265
|
+
}
|
|
230266
|
+
var RIVAL_HINTS = ["breaking", "openapi", "contract", "api diff", "api change", "schema diff"];
|
|
230267
|
+
function signalNoAbsorbingRival(tools, shipped = []) {
|
|
230268
|
+
const own = new Set(shipped.map((t) => bare(t.name)));
|
|
230269
|
+
const rivals = tools.filter((t) => bare(t.name) !== PREFLIGHT && !own.has(bare(t.name))).filter((t) => {
|
|
230270
|
+
const text = `${bare(t.name)} ${descriptionOf(t)}`.toLowerCase();
|
|
230271
|
+
return RIVAL_HINTS.some((h) => text.includes(h));
|
|
230272
|
+
}).map((t) => bare(t.name));
|
|
230273
|
+
return rivals.length === 0 ? { ok: true, detail: "no other tool advertises contract-change checking" } : {
|
|
230274
|
+
ok: false,
|
|
230275
|
+
warning: true,
|
|
230276
|
+
detail: `${rivals.length} other tool(s) advertise contract-change work and may absorb the call: ` + rivals.join(", ") + " \u2014 Tier 1 measures whether they actually do",
|
|
230277
|
+
fix: null
|
|
230278
|
+
};
|
|
230279
|
+
}
|
|
230280
|
+
function tier0(tools) {
|
|
230281
|
+
const h = harness();
|
|
230282
|
+
const shipped = h.getCanonicalTools();
|
|
230283
|
+
const signals = [
|
|
230284
|
+
{ id: "preflight_present", ...signalPreflightPresent(tools) },
|
|
230285
|
+
{ id: "description_matches_shipped", ...signalDescriptionMatchesShipped(tools, shipped) },
|
|
230286
|
+
{ id: "schema_valid", ...signalSchemaValid(tools, h) },
|
|
230287
|
+
{ id: "no_absorbing_rival", ...signalNoAbsorbingRival(tools, shipped) }
|
|
230288
|
+
];
|
|
230289
|
+
const blocking = signals.filter((s) => !s.ok && !s.warning);
|
|
230290
|
+
return {
|
|
230291
|
+
tier: 0,
|
|
230292
|
+
tool_count: tools.length,
|
|
230293
|
+
signals,
|
|
230294
|
+
passed: signals.filter((s) => s.ok).length,
|
|
230295
|
+
total: signals.length,
|
|
230296
|
+
blocking: blocking.length,
|
|
230297
|
+
fixes: [...new Set(signals.filter((s) => !s.ok && s.fix).map((s) => s.fix))]
|
|
230298
|
+
};
|
|
230299
|
+
}
|
|
230300
|
+
var DOES_NOT_PROVE = Object.freeze({
|
|
230301
|
+
0: [
|
|
230302
|
+
"that your model WOULD select CodeRifts \u2014 Tier 0 is structural only, a necessary condition",
|
|
230303
|
+
"that a change reached a merge/deploy/publish sink, or that any gate fired",
|
|
230304
|
+
"anything about tools this file was not shown"
|
|
230305
|
+
],
|
|
230306
|
+
1: [
|
|
230307
|
+
"that a change reached a merge/deploy/publish sink \u2014 selection is not enforcement",
|
|
230308
|
+
"anything about a model other than the one you ran, at the version you ran it",
|
|
230309
|
+
"that your production prompt behaves like the harness prompt"
|
|
230310
|
+
]
|
|
230311
|
+
});
|
|
230312
|
+
module2.exports = {
|
|
230313
|
+
parseToolsDocument,
|
|
230314
|
+
tier0,
|
|
230315
|
+
harness,
|
|
230316
|
+
bare,
|
|
230317
|
+
DOES_NOT_PROVE,
|
|
230318
|
+
PREFLIGHT,
|
|
230319
|
+
signalPreflightPresent,
|
|
230320
|
+
signalDescriptionMatchesShipped,
|
|
230321
|
+
signalSchemaValid,
|
|
230322
|
+
signalNoAbsorbingRival
|
|
230323
|
+
};
|
|
230324
|
+
}
|
|
230325
|
+
});
|
|
230326
|
+
|
|
230327
|
+
// src/commands/doctor.js
|
|
230328
|
+
var require_doctor2 = __commonJS({
|
|
230329
|
+
"src/commands/doctor.js"(exports2, module2) {
|
|
230330
|
+
"use strict";
|
|
230331
|
+
var fs = require("fs");
|
|
230332
|
+
var path = require("path");
|
|
230333
|
+
var chalk = require_source();
|
|
230334
|
+
var {
|
|
230335
|
+
parseToolsDocument,
|
|
230336
|
+
tier0,
|
|
230337
|
+
harness,
|
|
230338
|
+
DOES_NOT_PROVE
|
|
230339
|
+
} = require_doctor();
|
|
230340
|
+
function readToolsFile(file) {
|
|
230341
|
+
const resolved = path.resolve(file);
|
|
230342
|
+
if (!fs.existsSync(resolved)) return { ok: false, why: `no such file: ${file}` };
|
|
230343
|
+
let doc;
|
|
230344
|
+
try {
|
|
230345
|
+
doc = JSON.parse(fs.readFileSync(resolved, "utf8"));
|
|
230346
|
+
} catch (err) {
|
|
230347
|
+
return { ok: false, why: `not JSON: ${err && err.message || "parse error"}` };
|
|
230348
|
+
}
|
|
230349
|
+
return parseToolsDocument(doc);
|
|
230350
|
+
}
|
|
230351
|
+
function printBoundary(tier, log) {
|
|
230352
|
+
log("");
|
|
230353
|
+
log(chalk.bold(" WHAT THIS DOES NOT PROVE"));
|
|
230354
|
+
for (const line of DOES_NOT_PROVE[tier]) log(chalk.dim(` \xB7 ${line}`));
|
|
230355
|
+
}
|
|
230356
|
+
function renderTier0(r, log) {
|
|
230357
|
+
log("");
|
|
230358
|
+
log(chalk.bold(` coderifts doctor \u2014 structural fitness (${r.tool_count} tools, no model called)`));
|
|
230359
|
+
log("");
|
|
230360
|
+
for (const s of r.signals) {
|
|
230361
|
+
const mark = s.ok ? chalk.green(" ok ") : s.warning ? chalk.yellow(" !! ") : chalk.red(" X ");
|
|
230362
|
+
log(`${mark}${s.id}`);
|
|
230363
|
+
log(chalk.dim(` ${s.detail}`));
|
|
230364
|
+
}
|
|
230365
|
+
log("");
|
|
230366
|
+
const verdict = r.blocking === 0 ? chalk.green(` structurally fit to be selected: ${r.passed}/${r.total} signals`) : chalk.red(` NOT structurally fit: ${r.blocking} blocking signal(s), ${r.passed}/${r.total} passed`);
|
|
230367
|
+
log(verdict);
|
|
230368
|
+
if (r.fixes.length) {
|
|
230369
|
+
log("");
|
|
230370
|
+
log(chalk.bold(" NEXT COMMAND"));
|
|
230371
|
+
for (const f of r.fixes) log(` ${chalk.cyan(f)}`);
|
|
230372
|
+
}
|
|
230373
|
+
log("");
|
|
230374
|
+
log(chalk.dim(" Tier 0 is the NECESSARY condition, not selectability. To measure whether your"));
|
|
230375
|
+
log(chalk.dim(" model actually selects, run the 15 fixtures on your own key:"));
|
|
230376
|
+
log(chalk.dim(" coderifts doctor --tools <file> --model <model-id>"));
|
|
230377
|
+
}
|
|
230378
|
+
async function runTier1({ tools, model, log, mock }) {
|
|
230379
|
+
const h = harness();
|
|
230380
|
+
const fixtures = h.loadSelectionFixtures();
|
|
230381
|
+
log("");
|
|
230382
|
+
log(chalk.bold(` coderifts doctor \u2014 selection benchmark (${fixtures.length} fixtures, model ${model})`));
|
|
230383
|
+
let client;
|
|
230384
|
+
if (mock) {
|
|
230385
|
+
client = new h.MockClient(h.perfectScript());
|
|
230386
|
+
log(chalk.yellow(" MOCK RUN \u2014 a scripted model that answers perfectly by construction."));
|
|
230387
|
+
log(chalk.yellow(" This measures the script. It is not a measurement of any model."));
|
|
230388
|
+
} else {
|
|
230389
|
+
const provider = h.PROVIDERS && Object.keys(h.PROVIDERS).find((p) => String(model).toLowerCase().includes(p));
|
|
230390
|
+
const factory = provider === "openai" ? h.createOpenAIClient : provider === "google" ? h.createGoogleClient : provider === "xai" ? h.createXaiClient : h.createAnthropicClient;
|
|
230391
|
+
const envVar = provider === "openai" ? "OPENAI_API_KEY" : provider === "google" ? "GOOGLE_API_KEY" : provider === "xai" ? "XAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
230392
|
+
const key = process.env[envVar];
|
|
230393
|
+
if (!key) {
|
|
230394
|
+
log("");
|
|
230395
|
+
log(chalk.red(` ${envVar} is not set.`));
|
|
230396
|
+
log(chalk.dim(" Tier 1 runs on YOUR key, on YOUR machine. CodeRifts never sees it, and"));
|
|
230397
|
+
log(chalk.dim(" never pays for the run. Export the key and re-run, or use --mock for a"));
|
|
230398
|
+
log(chalk.dim(" no-cost dry run of the harness path."));
|
|
230399
|
+
return { ok: false, reason: "missing_key" };
|
|
230400
|
+
}
|
|
230401
|
+
client = factory({ apiKey: key, model });
|
|
230402
|
+
}
|
|
230403
|
+
const userNames = new Set(tools.map((t) => String(t.name || "").replace(/^coderifts\./, "")));
|
|
230404
|
+
const withUserTable = fixtures.map((f) => ({
|
|
230405
|
+
...f,
|
|
230406
|
+
available_tools: f.available_tools.filter((n) => userNames.has(String(n).replace(/^coderifts\./, "")))
|
|
230407
|
+
}));
|
|
230408
|
+
const unreachable = withUserTable.filter((f) => f.available_tools.length === 0).length;
|
|
230409
|
+
if (unreachable) {
|
|
230410
|
+
log(chalk.yellow(` ${unreachable} fixture(s) have no matching tool in your table \u2014 they can only score as a miss.`));
|
|
230411
|
+
}
|
|
230412
|
+
const unanswerable = withUserTable.filter((f) => {
|
|
230413
|
+
const want = String(f.expected_tool || "").replace(/^coderifts\./, "");
|
|
230414
|
+
return want && want !== "none" && !userNames.has(want);
|
|
230415
|
+
}).map((f) => f.id);
|
|
230416
|
+
if (unanswerable.length) {
|
|
230417
|
+
log(chalk.yellow(` ${unanswerable.length} fixture(s) expect a tool your table does not offer (${unanswerable.join(", ")}) \u2014 they CANNOT score correct. That is your table, not your model.`));
|
|
230418
|
+
}
|
|
230419
|
+
const run = await h.runBenchmark({ modelId: model, client, fixtures: withUserTable });
|
|
230420
|
+
const m = run && run.metrics ? run.metrics : h.computeMetrics(run.results || []);
|
|
230421
|
+
log("");
|
|
230422
|
+
for (const k of ["selection_coverage", "bypass_rate", "omission_rate", "wrong_tool_rate", "overall_accuracy"]) {
|
|
230423
|
+
if (m[k] !== void 0) log(` ${String(k).padEnd(20)} ${m[k]}`);
|
|
230424
|
+
}
|
|
230425
|
+
return { ok: true, metrics: m };
|
|
230426
|
+
}
|
|
230427
|
+
async function runDoctor(options = {}) {
|
|
230428
|
+
const log = options.logFn || ((s) => process.stdout.write(`${s}
|
|
230429
|
+
`));
|
|
230430
|
+
const exitFn = options.exitFn || ((c) => process.exit(c));
|
|
230431
|
+
if (!options.tools) {
|
|
230432
|
+
log(chalk.red(" --tools <file> is required: the tools/list your host actually serves."));
|
|
230433
|
+
log(chalk.dim(` Save it with your MCP client, or: curl .../mcp -d '{"method":"tools/list"}' > tools.json`));
|
|
230434
|
+
exitFn(2);
|
|
230435
|
+
return { exitCode: 2 };
|
|
230436
|
+
}
|
|
230437
|
+
const parsed = readToolsFile(options.tools);
|
|
230438
|
+
if (!parsed.ok) {
|
|
230439
|
+
log(chalk.red(` could not read the tool table: ${parsed.why}`));
|
|
230440
|
+
exitFn(2);
|
|
230441
|
+
return { exitCode: 2 };
|
|
230442
|
+
}
|
|
230443
|
+
if (options.model) {
|
|
230444
|
+
const r = await runTier1({ tools: parsed.tools, model: options.model, log, mock: !!options.mock });
|
|
230445
|
+
printBoundary(1, log);
|
|
230446
|
+
const code = r.ok ? 0 : 1;
|
|
230447
|
+
exitFn(code);
|
|
230448
|
+
return { exitCode: code, tier: 1, metrics: r.metrics };
|
|
230449
|
+
}
|
|
230450
|
+
const report = tier0(parsed.tools);
|
|
230451
|
+
renderTier0(report, log);
|
|
230452
|
+
printBoundary(0, log);
|
|
230453
|
+
exitFn(0);
|
|
230454
|
+
return { exitCode: 0, tier: 0, report };
|
|
230455
|
+
}
|
|
230456
|
+
module2.exports = { runDoctor, readToolsFile, renderTier0, printBoundary };
|
|
230457
|
+
}
|
|
230458
|
+
});
|
|
230459
|
+
|
|
230158
230460
|
// src/commands/publish-gate.js
|
|
230159
230461
|
var require_publish_gate = __commonJS({
|
|
230160
230462
|
"src/commands/publish-gate.js"(exports2, module2) {
|
|
@@ -264655,6 +264957,10 @@ program.command("deploy-gate").description("Gate a deploy on the current { envir
|
|
|
264655
264957
|
const { runDeployGate } = require_deploy_gate2();
|
|
264656
264958
|
await runDeployGate(options);
|
|
264657
264959
|
});
|
|
264960
|
+
program.command("doctor").description("Would an agent select CodeRifts for a contract change? Structural check (no key), or the 15-fixture benchmark on your own model.").option("--tools <file>", "Your host's tools/list response (array, { tools }, or { result: { tools } })").option("--model <id>", "TIER 1: run the fixtures through this model using YOUR key from the environment").option("--mock", "TIER 1 dry run: a deterministic scripted model. Measures the script, not a model.").action(async (options) => {
|
|
264961
|
+
const { runDoctor } = require_doctor2();
|
|
264962
|
+
await runDoctor(options);
|
|
264963
|
+
});
|
|
264658
264964
|
program.command("publish-gate").description("Gate npm publish on contract-artifact preflight (before=git baseline, after=working tree)").option("--spec <path>", "Contract artifact path (default: git config coderifts.specPath or api/openapi.yaml)").option("--json", "Machine-readable JSON result").action(async (options) => {
|
|
264659
264965
|
const { runPublishGate } = require_publish_gate();
|
|
264660
264966
|
const result = await runPublishGate(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderifts",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0",
|
|
4
4
|
"description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
5
5
|
"author": "CodeRifts <hello@coderifts.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "node --test test/*.test.js"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@coderifts/agent-guard": "^
|
|
49
|
+
"@coderifts/agent-guard": "^17.1.0",
|
|
50
50
|
"chalk": "^4.1.2",
|
|
51
51
|
"cli-table3": "^0.6.4",
|
|
52
52
|
"commander": "^12.0.0",
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
* asVerifiedDeployReceiptView, DEPLOY_RECEIPT_VIEW_SPEC and matchGlob — no DeployGateReason
|
|
74
74
|
* exhaustive switch, no V2 config-conflict throw. The raise is installer-facing: the range
|
|
75
75
|
* is what `npm i -g coderifts` resolves.)
|
|
76
|
+
* 8.2.0: floor raised to 17 (published guard 17.0.0 is on npm; 17 blocks raw shell carrying a
|
|
77
|
+
* mutation by default — a host-declared mutating_shell now reaches the block instead of dying at
|
|
78
|
+
* the descriptor boundary. The CLI range must match so a fresh install gets the guard whose
|
|
79
|
+
* default the docs describe.)
|
|
76
80
|
* 8.1.0: floor raised to 16 (published guard 16.0.0 is on npm; 16 makes UNGUARDED_MUTATION
|
|
77
81
|
* fail-closed the default. The CLI range must match so `npm i -g coderifts` installs the guard
|
|
78
82
|
* whose default the docs describe, not the 15.x advisory-default it shipped alongside before.)
|
|
@@ -82,7 +86,7 @@ const fs = require('fs');
|
|
|
82
86
|
const path = require('path');
|
|
83
87
|
|
|
84
88
|
/** Minimum acceptable major for @coderifts/agent-guard in THIS package (the CLI). */
|
|
85
|
-
const MIN_GUARD_MAJOR =
|
|
89
|
+
const MIN_GUARD_MAJOR = 17;
|
|
86
90
|
const DEP = '@coderifts/agent-guard';
|
|
87
91
|
|
|
88
92
|
/**
|