coderifts 3.2.0 → 3.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/README.md CHANGED
@@ -94,6 +94,57 @@ is **never** treated as a silent NEW_ARTIFACT pass.
94
94
 
95
95
  On success the command prints a **receipt reference** when the preflight path issues one.
96
96
 
97
+ ### `coderifts deploy-gate`
98
+
99
+ Gate a **deploy** on the current `{ environment, artifact }` using a preflight
100
+ receipt (phase-1 advisory). The CD sibling of `publish-gate` / the merge-gate
101
+ App check. Composes the validated `deployGate` from `@coderifts/agent-guard` —
102
+ the CLI only binds env + artifact + receipt + observed CD enforcement. No
103
+ scoring is reimplemented here.
104
+
105
+ Phase 1 **reports** and exits **0** even on a gate deny, unless the step is
106
+ marked enforcing (`--enforce` or `CODERIFTS_DEPLOY_ENFORCE=true`), in which
107
+ case a failure exits **1**. Missing `--env` / `--artifact` is a usage error
108
+ (exit **1**). No receipt → pending (never a fabricated allow).
109
+
110
+ | Flag | Description | Default |
111
+ |------|-------------|---------|
112
+ | `--env <environment>` | Target environment (e.g. `production`, `staging`) | required |
113
+ | `--artifact <artifact_id>` | Immutable artifact identity (content digest or commit SHA) | required |
114
+ | `--receipt <file>` | Path to the deploy-scoped receipt JSON from preflight | none |
115
+ | `--json` | Binding result as JSON | `false` |
116
+ | `--enforce` | Attest ENFORCING and exit non-zero on a gate failure | `false` |
117
+
118
+ ```bash
119
+ coderifts deploy-gate --env production --artifact "$SHA" --receipt receipt.json
120
+ coderifts deploy-gate --env production --artifact "$SHA" --receipt receipt.json --enforce
121
+ ```
122
+
123
+ ### `coderifts outcome <kind>`
124
+
125
+ Report a **post-hoc** observed outcome for a past `decision_id` to
126
+ `POST /api/v1/outcomes`. Records the **caller's assertion** (your deploy job
127
+ knows success/failure) — the command does **not** itself verify the deploy.
128
+
129
+ `kind` is a closed set: `deploy_succeeded`, `deploy_failed`, `rolled_back`,
130
+ `consumer_break_reported`, `remediation_verified_working`,
131
+ `false_positive_reported`, `other_reported`.
132
+
133
+ Requires a cloud API key (`coderifts login` or `CODERIFTS_API_KEY`). Reporter
134
+ is derived from the key. Exit **0** on success, **1** on usage / API error.
135
+
136
+ | Flag | Description | Default |
137
+ |------|-------------|---------|
138
+ | `--decision <decision_id>` | `decision_result.decision_id` (required) | none |
139
+ | `--observed-at <iso>` | When the outcome was observed (ISO-8601) | now |
140
+ | `--details <json>` | Optional JSON object/array bound to the row | none |
141
+ | `--json` | Print the API response JSON | `false` |
142
+
143
+ ```bash
144
+ coderifts outcome deploy_succeeded --decision "$DECISION_ID"
145
+ coderifts outcome deploy_failed --decision "$DECISION_ID" --json
146
+ ```
147
+
97
148
  ### `coderifts registry-gate [dir]`
98
149
 
99
150
  Local **registry admission gate** for a directory of OpenAPI/Swagger specs
@@ -185,6 +236,64 @@ git config coderifts.specPath api/openapi.yaml # default if omitted
185
236
  - **User** `~/.claude/settings.json` — personal; applies to all projects on this machine.
186
237
  - Matcher is case-sensitive tool names/regex. Exit-2 blocking is **Claude Code** semantics; the same decision discipline at push-time is the git pre-push hook above.
187
238
 
239
+ **`CODERIFTS_STRICT` (opt-in, not a package guarantee):** env `1` or `true`
240
+ (case-insensitive) hardens **enforcement-relevant** soft sites into **exit 2
241
+ (BLOCK)** — “governance did not run” must not read as ALLOW. Hardened: missing
242
+ API key; API unreachable (network/timeout/error); disk unreadable on the
243
+ contract path; Edit/MultiEdit apply failure on the contract path. Still soft
244
+ even in strict: non-spec path, identical content, unparseable stdin, missing
245
+ `file_path`, WARN/REQUIRE_APPROVAL (governance ran), real ALLOW. Unset/empty →
246
+ identical to the default soft UX. Hosts choose strict for CI/prod.
247
+
248
+ ```bash
249
+ CODERIFTS_STRICT=1 coderifts claude-hook
250
+ ```
251
+
252
+ ### `coderifts status [repo]`
253
+
254
+ **Read-only** cross-layer enforcement report (Runtime / Merge / Deploy, plus
255
+ Content). Calls `GET /api/v1/enforcement-status` and prints the statuses the
256
+ endpoint measured — no GitHub writes, no branch-protection changes. Pair with
257
+ `coderifts enforce` (that command **acts**; this one only **shows**).
258
+
259
+ Requires a cloud API key. Distinct from `coderifts hook status` (local
260
+ pre-push hook install state). Exit **0** on a successful read, **1** on
261
+ missing/invalid repo, missing key, or API error.
262
+
263
+ | Flag | Description | Default |
264
+ |------|-------------|---------|
265
+ | `[repo]` / `--repo <owner/repo>` | Repository | none (required) |
266
+ | `--json` | Machine-readable JSON (raw API body) | `false` |
267
+
268
+ ```bash
269
+ coderifts status owner/repo
270
+ coderifts status --repo owner/repo --json
271
+ ```
272
+
273
+ ### `coderifts enforce [repo]`
274
+
275
+ Close enforcement gaps by chaining existing setup commands: Merge →
276
+ `setup-required-check`; Runtime → `hook install` (apply only) + agent-guard
277
+ guidance; Deploy → `deploy-gate` CD-step guidance; Content → guidance only.
278
+ **Dry-run by default** (`--apply` to mutate). Does not bypass underlying
279
+ command safety. Does not label Runtime/Content as server ENFORCING. Does not
280
+ upgrade UNKNOWN.
281
+
282
+ Requires a cloud API key for the status read. Merge `--apply` uses your local
283
+ `gh` credentials, not the CodeRifts API key. Exit **0** when no chained step
284
+ failed, **1** on missing repo/key, API error, or any apply failure.
285
+
286
+ | Flag | Description | Default |
287
+ |------|-------------|---------|
288
+ | `[repo]` / `--repo <owner/repo>` | Repository | none (required) |
289
+ | `--apply` | Run underlying setup commands | dry-run |
290
+ | `--json` | Machine-readable JSON result | `false` |
291
+
292
+ ```bash
293
+ coderifts enforce owner/repo
294
+ coderifts enforce --repo owner/repo --apply
295
+ ```
296
+
188
297
  ### Agent host files (`coderifts agent-setup`)
189
298
 
190
299
  Writes the six CodeRifts agent-host rule files into the current repo (or `--out <dir>`):
@@ -210,6 +319,28 @@ re-sync with `node scripts/sync-cli-agent-host-embed.js` when the rule source ch
210
319
 
211
320
  Policy template for agent/MCP APIs: `coderifts init ai-agent` (aliases: `agent`, `mcp`).
212
321
 
322
+ ### Copilot MCP configs (`coderifts copilot-setup`)
323
+
324
+ Writes GitHub Copilot MCP configs from the canonical tool names. Root keys
325
+ differ: VS Code uses `servers`; Copilot cloud Settings paste uses `mcpServers`.
326
+
327
+ ```bash
328
+ coderifts copilot-setup # write (skip existing files)
329
+ coderifts copilot-setup --force # overwrite
330
+ coderifts copilot-setup --check # drift vs embedded content (exit 1 on drift)
331
+ coderifts copilot-setup --out ./docs # custom root
332
+ ```
333
+
334
+ | Flag | Description | Default |
335
+ |------|-------------|---------|
336
+ | `--out <dir>` | Target directory | cwd |
337
+ | `--check` | Exit 0 if on-disk files match embedded content; exit 1 on drift | `false` |
338
+ | `--force` | Overwrite existing files | skip collisions |
339
+
340
+ Writes: `.vscode/mcp.json`, `copilot-cloud-agent-mcp.json`,
341
+ `copilot-custom-agent-mcp.frontmatter.md`, `docs/copilot-mcp.md`. Unknown flags
342
+ exit 1 (never silently ignored).
343
+
213
344
  ### Required check wizard (`coderifts setup-required-check`)
214
345
 
215
346
  Guided setup so **CodeRifts / contract-gate** is a **required status check** on
@@ -245,6 +376,57 @@ coderifts setup-required-check --apply
245
376
  coderifts setup-required-check --branch main
246
377
  ```
247
378
 
379
+ ### `coderifts lock [repo]`
380
+
381
+ Fetch the observed agent-contract lockfile (`coderifts.lock` v1) from
382
+ `GET /api/v1/lock`, **or** `--check` MCP drift of a locked manifest against a
383
+ live one (`scoreMcpRisk`). `--check` is **monitor only** — it does not block.
384
+ Unreachable live → status `unreachable` (not drift). Lock v1 records
385
+ agents/ops, not MCP tool bodies; pass `--locked-manifest` (or additive
386
+ `mcp_manifests` on the lockfile) for `--check`.
387
+
388
+ Requires a cloud API key for write mode.
389
+
390
+ | Flag | Description | Default |
391
+ |------|-------------|---------|
392
+ | `[repo]` / `--repo <owner/repo>` | Repository | none (write mode) |
393
+ | `--out <path>` | Output path | `coderifts.lock` |
394
+ | `--json` | stdout-only JSON (write: lock doc; check: drift report) | `false` |
395
+ | `--check` | Compare locked vs live MCP manifest | `false` |
396
+ | `--locked-manifest <path>` | Baseline MCP manifest JSON (`tools[]`) for `--check` | none |
397
+ | `--live-manifest <path>` | Live MCP manifest JSON for `--check` | none |
398
+ | `--live-url <url>` | Fetch live MCP manifest JSON from URL for `--check` | none |
399
+ | `--lockfile <path>` | Read `coderifts.lock` for additive `mcp_manifests` | none |
400
+
401
+ ```bash
402
+ coderifts lock owner/repo
403
+ coderifts lock --check --locked-manifest mcp.json --live-manifest live.json
404
+ ```
405
+
406
+ **`--check` exit codes** (from `lock.js`): `0` unchanged; `3` drift; `2`
407
+ unreachable / missing locked or live (not clean); `1` usage/error.
408
+
409
+ ### `coderifts adopt`
410
+
411
+ Counterfactual report: what CodeRifts **would have done** on contract-artifact
412
+ changes in **local git** history. Extracts spec-only paths (not source files
413
+ whose names happen to contain “openapi”), then runs
414
+ `buildCounterfactualReport`. Step B (GitHub API + outcome correlation) is not
415
+ this command. Not a git repository → fail-closed (`GIT_ERROR`, exit **1**).
416
+ Success → exit **0**.
417
+
418
+ | Flag | Description | Default |
419
+ |------|-------------|---------|
420
+ | `--days <n>` | Look back N days (`git log --since`) | last 50 commits if neither flag set |
421
+ | `--commits <n>` | Last N commits | last 50 commits if neither flag set |
422
+ | `--json` | Machine-readable JSON report (analyzer logs silenced) | `false` |
423
+
424
+ ```bash
425
+ coderifts adopt
426
+ coderifts adopt --days 30
427
+ coderifts adopt --commits 20 --json
428
+ ```
429
+
248
430
  ## CI/CD Integration
249
431
 
250
432
  ### GitHub Actions
@@ -303,14 +485,18 @@ Two honest modes:
303
485
  |----------|-------------|
304
486
  | `CODERIFTS_API_KEY` | API key (alternative to `coderifts login`) |
305
487
  | `CODERIFTS_OASDIFF_BIN` | Explicit path to the `oasdiff` binary (overrides auto-detect) |
488
+ | `CODERIFTS_STRICT` | `claude-hook` only: `1` or `true` (case-insensitive) turns enforcement-relevant soft sites into exit **2** (BLOCK). Unset/empty → default soft allow. Not a package-level guarantee. |
489
+ | `CODERIFTS_DEPLOY_ENFORCE` | `deploy-gate`: `true` marks the step enforcing (same as `--enforce`); `unknown` is explicitly unobservable (never assume ENFORCING) |
306
490
  | `NO_COLOR` | Disable colored output |
307
491
 
308
492
  ## Exit Codes
309
493
 
310
494
  | Code | Meaning |
311
495
  |------|---------|
312
- | `0` | No breaking changes (or below threshold) |
313
- | `1` | Breaking changes found (in CI mode) or analysis failed |
496
+ | `0` | Success: no breaking changes (or below threshold); advisory `deploy-gate`; `lock --check` unchanged; `adopt` / `status` / `outcome` success |
497
+ | `1` | Breaking changes (CI mode), analysis/usage/API failure, or `deploy-gate` deny **when enforcing** |
498
+ | `2` | `claude-hook` BLOCK (Claude Code cancel); `lock --check` unreachable / missing locked or live |
499
+ | `3` | `lock --check` drift detected (monitor only — does not block the package) |
314
500
 
315
501
  ## Links
316
502
 
package/bin/coderifts.js CHANGED
@@ -183,14 +183,21 @@ program
183
183
  runCopilotSetup(options, { exit: true });
184
184
  });
185
185
 
186
- // ── lock — agent contract lockfile v1 (observed usage; ID847) ──
187
- // GET /api/v1/lock?repo= → write coderifts.lock. Observed-only; empty agents when none recorded.
186
+ // ── lock — agent contract lockfile v1 (observed usage; ID847) + MCP drift --check (896) ──
187
+ // Write: GET /api/v1/lock?repo= → coderifts.lock. Check: locked MCP vs live via scoreMcpRisk.
188
188
  program
189
189
  .command('lock [repo]')
190
- .description('Fetch the observed agent-contract lockfile (coderifts.lock v1) for a repo')
190
+ .description(
191
+ 'Fetch observed agent-contract lockfile (coderifts.lock v1), or --check MCP drift vs live manifest',
192
+ )
191
193
  .option('--repo <owner/repo>', 'Repository (owner/repo); also accepted as a positional argument')
192
194
  .option('--out <path>', 'Output path (default: coderifts.lock in cwd)')
193
- .option('--json', 'Print the lock document JSON to stdout (still writes --out)')
195
+ .option('--json', 'Print JSON to stdout only (write: lock doc; check: drift report)')
196
+ .option('--check', 'MCP supply-chain monitor: compare locked vs live manifest (scoreMcpRisk)')
197
+ .option('--locked-manifest <path>', 'Baseline MCP manifest JSON (tools[]) for --check')
198
+ .option('--live-manifest <path>', 'Live MCP manifest JSON for --check')
199
+ .option('--live-url <url>', 'Fetch live MCP manifest JSON from URL for --check')
200
+ .option('--lockfile <path>', 'Read coderifts.lock for additive mcp_manifests (optional)')
194
201
  .action(async (repoPositional, options) => {
195
202
  const { runLock } = require('../src/commands/lock');
196
203
  const result = await runLock({
@@ -202,6 +209,29 @@ program
202
209
  }
203
210
  });
204
211
 
212
+ // ── adopt — counterfactual "what would CodeRifts have done" from local git (roadmap 893 Step A) ──
213
+ // Stateless core in app src/core/counterfactual-adopt.js; CLI extracts change sets via git
214
+ // (mirrors publish-gate gitImpl). Not named "shadow" (shadowApis is taken).
215
+ program
216
+ .command('adopt')
217
+ .description(
218
+ 'Counterfactual report: what CodeRifts would have done on contract-artifact changes in local git history',
219
+ )
220
+ .option('--days <n>', 'Look back N days of git history', (v) => parseInt(v, 10))
221
+ .option('--commits <n>', 'Look at the last N commits', (v) => parseInt(v, 10))
222
+ .option('--json', 'Machine-readable JSON report')
223
+ .action(async (options) => {
224
+ const { runAdopt } = require('../src/commands/adopt');
225
+ const result = await runAdopt({
226
+ days: options.days,
227
+ commits: options.commits,
228
+ json: !!options.json,
229
+ });
230
+ if (result && typeof result.exitCode === 'number') {
231
+ process.exitCode = result.exitCode;
232
+ }
233
+ });
234
+
205
235
  // ── outcome — POST /api/v1/outcomes (caller assertion; does not verify the deploy) ──
206
236
  program
207
237
  .command('outcome <kind>')