coderifts 3.0.0 → 3.1.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/bin/coderifts.js CHANGED
@@ -169,6 +169,38 @@ program
169
169
  runAgentSetup(options, { exit: true });
170
170
  });
171
171
 
172
+ // ── copilot-setup — emit GitHub Copilot MCP configs (VS Code + cloud agent + custom agent) ──
173
+ // Single-source from CANONICAL_TOOL_NAMES; root keys: servers (VS Code) vs mcpServers (cloud).
174
+ program
175
+ .command('copilot-setup')
176
+ .description('Write GitHub Copilot MCP configs (.vscode/mcp.json + cloud-agent paste JSON + docs)')
177
+ .option('--out <dir>', 'Target directory (default: current working directory)')
178
+ .option('--check', 'Exit 0 if on-disk files match embedded content; exit 1 on drift')
179
+ .option('--force', 'Overwrite existing files (default: skip collisions)')
180
+ .action((options) => {
181
+ const { runCopilotSetup } = require('../src/commands/copilot-setup');
182
+ runCopilotSetup(options, { exit: true });
183
+ });
184
+
185
+ // ── lock — agent contract lockfile v1 (observed usage; ID847) ──
186
+ // GET /api/v1/lock?repo= → write coderifts.lock. Observed-only; empty agents when none recorded.
187
+ program
188
+ .command('lock [repo]')
189
+ .description('Fetch the observed agent-contract lockfile (coderifts.lock v1) for a repo')
190
+ .option('--repo <owner/repo>', 'Repository (owner/repo); also accepted as a positional argument')
191
+ .option('--out <path>', 'Output path (default: coderifts.lock in cwd)')
192
+ .option('--json', 'Print the lock document JSON to stdout (still writes --out)')
193
+ .action(async (repoPositional, options) => {
194
+ const { runLock } = require('../src/commands/lock');
195
+ const result = await runLock({
196
+ ...options,
197
+ repo: options.repo || repoPositional || null,
198
+ });
199
+ if (result && typeof result.exitCode === 'number') {
200
+ process.exitCode = result.exitCode;
201
+ }
202
+ });
203
+
172
204
  // ── hook command group ──
173
205
  const hookCmd = program
174
206
  .command('hook')
package/dist/cli.js CHANGED
@@ -3007,7 +3007,7 @@ var require_package = __commonJS({
3007
3007
  "package.json"(exports2, module2) {
3008
3008
  module2.exports = {
3009
3009
  name: "coderifts",
3010
- version: "3.0.0",
3010
+ version: "3.1.0",
3011
3011
  description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
3012
3012
  author: "CodeRifts <hello@coderifts.com>",
3013
3013
  license: "MIT",
@@ -13549,10 +13549,15 @@ var require_cloud = __commonJS({
13549
13549
  const q = encodeURIComponent(String(repo || ""));
13550
13550
  return cloudRequest("GET", `/api/v1/enforcement-status?repo=${q}`, apiKey);
13551
13551
  }
13552
+ function cloudGetLock(repo, apiKey) {
13553
+ const q = encodeURIComponent(String(repo || ""));
13554
+ return cloudRequest("GET", `/api/v1/lock?repo=${q}`, apiKey);
13555
+ }
13552
13556
  module2.exports = {
13553
13557
  cloudDiff,
13554
13558
  cloudRequest,
13555
13559
  cloudGetEnforcementStatus,
13560
+ cloudGetLock,
13556
13561
  API_BASE
13557
13562
  };
13558
13563
  }
@@ -97229,6 +97234,313 @@ ${USAGE}` };
97229
97234
  }
97230
97235
  });
97231
97236
 
97237
+ // src/copilot-mcp.embedded.js
97238
+ var require_copilot_mcp_embedded = __commonJS({
97239
+ "src/copilot-mcp.embedded.js"(exports2, module2) {
97240
+ "use strict";
97241
+ var COPILOT_MCP_PATHS = Object.freeze([
97242
+ ".vscode/mcp.json",
97243
+ "copilot-cloud-agent-mcp.json",
97244
+ "copilot-custom-agent-mcp.frontmatter.md",
97245
+ "docs/copilot-mcp.md"
97246
+ ]);
97247
+ var COPILOT_MCP_FILES = Object.freeze({
97248
+ ".vscode/mcp.json": '{\n "$comment": "GENERATED by scripts/generate-copilot-mcp.js from MCP_MANIFEST / CANONICAL_TOOL_NAMES (src/routes/mcp-streamable.js). Do not hand-edit; re-run the generator.",\n "servers": {\n "coderifts": {\n "type": "http",\n "url": "https://app.coderifts.com/mcp",\n "headers": {\n "Authorization": "Bearer ${input:coderifts_api_key}"\n }\n }\n },\n "inputs": [\n {\n "type": "promptString",\n "id": "coderifts_api_key",\n "description": "CodeRifts API key (https://coderifts.com) \u2014 Authorization Bearer",\n "password": true\n }\n ]\n}\n',
97249
+ "copilot-cloud-agent-mcp.json": '{\n "mcpServers": {\n "coderifts": {\n "type": "http",\n "url": "https://app.coderifts.com/mcp",\n "tools": [\n "preflight_change_set",\n "verify_receipt",\n "get_decision_details"\n ],\n "headers": {\n "Authorization": "Bearer ${COPILOT_MCP_CODERIFTS_API_KEY}"\n }\n }\n }\n}\n',
97250
+ "copilot-custom-agent-mcp.frontmatter.md": "---\n# GENERATED by scripts/generate-copilot-mcp.js from MCP_MANIFEST / CANONICAL_TOOL_NAMES (src/routes/mcp-streamable.js). Do not hand-edit; re-run the generator.\nname: coderifts-governance\ndescription: >\n CodeRifts API governance \u2014 preflight contract change sets before merge/deploy/publish,\n verify receipts, look up prior decisions. Branch on execution_action only.\ntools: ['coderifts/preflight_change_set', 'coderifts/verify_receipt', 'coderifts/get_decision_details']\nmcp-servers:\n coderifts:\n type: http\n url: https://app.coderifts.com/mcp\n tools:\n - preflight_change_set\n - verify_receipt\n - get_decision_details\n headers:\n Authorization: Bearer ${{ secrets.COPILOT_MCP_CODERIFTS_API_KEY }}\n---\n\nYou are a CodeRifts-aware agent. Before merge, deploy, publish, or tool registration when\ncontract artifacts changed, call `coderifts/preflight_change_set` with the complete base\u2192head\nchange set. Branch on `execution_action` only (CONTINUE, CONTINUE_WITH_MONITORING,\nREQUEST_APPROVAL, STOP). Do not treat `decision` or `safe_for_agent` as control flow.\nUse `coderifts/verify_receipt` to check an existing receipt; `coderifts/get_decision_details`\nfor a prior decision_id.\n",
97251
+ "docs/copilot-mcp.md": '# CodeRifts + GitHub Copilot MCP\n\n<!-- GENERATED by scripts/generate-copilot-mcp.js from MCP_MANIFEST / CANONICAL_TOOL_NAMES (src/routes/mcp-streamable.js). Do not hand-edit; re-run the generator. -->\n\nWire the **hosted** CodeRifts MCP server (`https://app.coderifts.com/mcp`) into every Copilot surface from **one**\nsource of truth (`CANONICAL_TOOL_NAMES` in `src/routes/mcp-streamable.js`).\n\n## Canonical tools (live tools/list)\n\n- `preflight_change_set`\n- `verify_receipt`\n- `get_decision_details`\n\nDo **not** list hidden/advanced aliases here. Only these 3 tools are the default\nagent-facing surface.\n\n## The servers-vs-mcpServers trap\n\n| Surface | Config location | Root key | Auth |\n|---------|-----------------|----------|------|\n| **VS Code / Copilot Chat** | `.vscode/mcp.json` | **`servers`** | `${input:coderifts_api_key}` + `inputs[]` |\n| **Copilot cloud agent + code review** | Repo **Settings \u2192 Copilot \u2192 MCP servers** (paste JSON) | **`mcpServers`** | Agents secret `COPILOT_MCP_CODERIFTS_API_KEY` in `headers` |\n| **Custom agent** (org/enterprise) | Agent profile `.md` YAML frontmatter | **`mcp-servers`** | `${{ secrets.COPILOT_MCP_CODERIFTS_API_KEY }}` |\n\nCursor / Claude Desktop / Grok use `mcpServers` in their own files \u2014 that is a **different**\necosystem. Do not copy a Cursor config into `.vscode/mcp.json`, and do not paste a VS Code\n`servers` document into GitHub repo Settings.\n\n## 1. VS Code / Copilot Chat (developer surface)\n\n1. Get an API key at https://coderifts.com\n2. Write the generated file to `.vscode/mcp.json` (or run `coderifts copilot-setup`)\n3. Reload VS Code; when prompted, paste the API key for `coderifts_api_key`\n4. In Copilot Chat Agent mode, confirm tools: preflight_change_set, verify_receipt, get_decision_details\n\n```json\n{\n "$comment": "GENERATED by scripts/generate-copilot-mcp.js from MCP_MANIFEST / CANONICAL_TOOL_NAMES (src/routes/mcp-streamable.js). Do not hand-edit; re-run the generator.",\n "servers": {\n "coderifts": {\n "type": "http",\n "url": "https://app.coderifts.com/mcp",\n "headers": {\n "Authorization": "Bearer ${input:coderifts_api_key}"\n }\n }\n },\n "inputs": [\n {\n "type": "promptString",\n "id": "coderifts_api_key",\n "description": "CodeRifts API key (https://coderifts.com) \u2014 Authorization Bearer",\n "password": true\n }\n ]\n}\n```\n\n## 2. Copilot cloud agent (PR / issue governance surface)\n\n1. Repo **Settings \u2192 Copilot \u2192 MCP servers** (or **Cloud agent \u2192 MCP configuration**)\n2. Paste the JSON below (root key **`mcpServers`**, includes required `tools` allowlist)\n3. Add an Agents secret: name `COPILOT_MCP_CODERIFTS_API_KEY`, value = CodeRifts API key\n4. Save. Validate: assign an issue to Copilot \u2192 session logs \u2192 **Start MCP Servers**\n\n```json\n{\n "mcpServers": {\n "coderifts": {\n "type": "http",\n "url": "https://app.coderifts.com/mcp",\n "tools": [\n "preflight_change_set",\n "verify_receipt",\n "get_decision_details"\n ],\n "headers": {\n "Authorization": "Bearer ${COPILOT_MCP_CODERIFTS_API_KEY}"\n }\n }\n }\n}\n```\n\nNotes:\n\n- Cloud agent does **not** support OAuth remote MCP; Bearer is correct for CodeRifts.\n- Cloud agent does **not** support interactive `inputs` \u2014 use Agents secrets only.\n- `tools` is required; list the 3 canonical tools (or `["*"]` only if you accept every tool the server exposes).\n\n## 3. Custom agent (optional org/enterprise)\n\nAdd `mcp-servers` to the agent profile frontmatter (see generated\n`copilot-custom-agent-mcp.frontmatter.md`). Tool names may be namespaced as\n`coderifts/<tool>` in the profile `tools` list.\n\n## Regenerate / drift-check\n\n```bash\nnode scripts/generate-copilot-mcp.js\nnode scripts/generate-copilot-mcp.js --check\ncoderifts copilot-setup --out . # write into a repo\ncoderifts copilot-setup --check # drift vs embedded\n```\n\nServer URL and tool names always come from the live manifest \u2014 never hand-edit tool lists.\n'
97252
+ });
97253
+ module2.exports = { COPILOT_MCP_FILES, COPILOT_MCP_PATHS };
97254
+ }
97255
+ });
97256
+
97257
+ // src/commands/copilot-setup.js
97258
+ var require_copilot_setup = __commonJS({
97259
+ "src/commands/copilot-setup.js"(exports2, module2) {
97260
+ "use strict";
97261
+ var fs = require("fs");
97262
+ var path = require("path");
97263
+ var chalk = require_source();
97264
+ var { COPILOT_MCP_FILES, COPILOT_MCP_PATHS } = require_copilot_mcp_embedded();
97265
+ if (process.env.NO_COLOR) chalk.level = 0;
97266
+ var USAGE = `Usage: coderifts copilot-setup [--out <dir>] [--check] [--force]
97267
+
97268
+ --out <dir> Target directory (default: current working directory)
97269
+ --check Exit 0 if on-disk files match embedded content; exit 1 on drift
97270
+ --force Overwrite existing files (default: skip collisions)
97271
+ Unknown flags exit 1 (never silently ignored).
97272
+
97273
+ Writes:
97274
+ .vscode/mcp.json VS Code / Copilot Chat (root key: servers)
97275
+ copilot-cloud-agent-mcp.json Paste into Settings \u2192 Copilot \u2192 MCP (mcpServers)
97276
+ copilot-custom-agent-mcp.frontmatter.md Custom agent YAML frontmatter
97277
+ docs/copilot-mcp.md Install guide
97278
+ `;
97279
+ function parseCopilotSetupArgs(argv) {
97280
+ const args = argv.slice(2);
97281
+ let out = null;
97282
+ let check = false;
97283
+ let force = false;
97284
+ let i = 0;
97285
+ while (i < args.length && !String(args[i]).startsWith("-")) i += 1;
97286
+ while (i < args.length) {
97287
+ const a = args[i];
97288
+ if (a === "--out") {
97289
+ const v = args[i + 1];
97290
+ if (v == null || v.startsWith("-")) {
97291
+ return { out, check, force, error: `copilot-setup: --out requires a path
97292
+ ${USAGE}` };
97293
+ }
97294
+ out = path.resolve(v);
97295
+ i += 2;
97296
+ continue;
97297
+ }
97298
+ if (a === "--check") {
97299
+ check = true;
97300
+ i += 1;
97301
+ continue;
97302
+ }
97303
+ if (a === "--force") {
97304
+ force = true;
97305
+ i += 1;
97306
+ continue;
97307
+ }
97308
+ if (a.startsWith("-")) {
97309
+ return { out, check, force, error: `copilot-setup: unrecognized argument: ${a}
97310
+ ${USAGE}` };
97311
+ }
97312
+ return { out, check, force, error: `copilot-setup: unrecognized argument: ${a}
97313
+ ${USAGE}` };
97314
+ }
97315
+ return { out, check, force };
97316
+ }
97317
+ function runCopilotSetup(options = {}, deps = {}) {
97318
+ const log = deps.log || console.log.bind(console);
97319
+ const logErr = deps.logErr || console.error.bind(console);
97320
+ const doExit = deps.exit !== false;
97321
+ const cwd = deps.cwd || process.cwd();
97322
+ const exists = deps.exists || fs.existsSync.bind(fs);
97323
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
97324
+ const writeFile = deps.writeFile || ((p, c) => {
97325
+ fs.mkdirSync(path.dirname(p), { recursive: true });
97326
+ fs.writeFileSync(p, c, "utf8");
97327
+ });
97328
+ let outDir = options.out ? path.resolve(String(options.out)) : cwd;
97329
+ let check = !!options.check;
97330
+ let force = !!options.force;
97331
+ if (deps.argv) {
97332
+ const parsed = parseCopilotSetupArgs(deps.argv);
97333
+ if (parsed.error) {
97334
+ logErr(parsed.error.trimEnd());
97335
+ if (doExit) process.exit(1);
97336
+ return { exitCode: 1, code: "USAGE", message: parsed.error };
97337
+ }
97338
+ if (parsed.out) outDir = parsed.out;
97339
+ check = parsed.check;
97340
+ force = parsed.force;
97341
+ }
97342
+ const files = deps.files || COPILOT_MCP_FILES;
97343
+ const relPaths = deps.paths || COPILOT_MCP_PATHS;
97344
+ if (check) {
97345
+ let stale = false;
97346
+ for (const rel of relPaths) {
97347
+ const fp = path.join(outDir, rel);
97348
+ const expected = files[rel];
97349
+ if (!exists(fp)) {
97350
+ logErr(chalk.red(`copilot-setup --check: missing ${rel}`));
97351
+ stale = true;
97352
+ continue;
97353
+ }
97354
+ const onDisk = readFile(fp);
97355
+ if (onDisk !== expected) {
97356
+ logErr(chalk.red(`copilot-setup --check: drift ${rel}`));
97357
+ stale = true;
97358
+ }
97359
+ }
97360
+ if (stale) {
97361
+ logErr("Run: coderifts copilot-setup --out " + outDir + " --force");
97362
+ if (doExit) process.exit(1);
97363
+ return { exitCode: 1, code: "DRIFT", outDir };
97364
+ }
97365
+ log(chalk.green(`copilot-setup: up to date (${outDir}, ${relPaths.length} files)`));
97366
+ if (doExit) process.exit(0);
97367
+ return { exitCode: 0, code: "UP_TO_DATE", outDir };
97368
+ }
97369
+ const summary = { written: [], skipped: [], forced: [] };
97370
+ for (const rel of relPaths) {
97371
+ const fp = path.join(outDir, rel);
97372
+ const content = files[rel];
97373
+ if (exists(fp) && !force) {
97374
+ summary.skipped.push(rel);
97375
+ continue;
97376
+ }
97377
+ if (exists(fp) && force) summary.forced.push(rel);
97378
+ writeFile(fp, content);
97379
+ summary.written.push(rel);
97380
+ }
97381
+ if (!options.json) {
97382
+ log(chalk.bold("CodeRifts copilot-setup"));
97383
+ log(` target: ${outDir}`);
97384
+ for (const rel of summary.written) {
97385
+ const tag = summary.forced.includes(rel) ? "overwrote" : "wrote";
97386
+ log(chalk.green(` ${tag}: ${rel}`));
97387
+ }
97388
+ for (const rel of summary.skipped) {
97389
+ log(chalk.yellow(` skipped: ${rel} (exists; use --force to overwrite)`));
97390
+ }
97391
+ log("");
97392
+ log(chalk.dim(' VS Code: open .vscode/mcp.json \u2014 root key is "servers"'));
97393
+ log(chalk.dim(" Cloud: paste copilot-cloud-agent-mcp.json into Settings \u2192 Copilot \u2192 MCP"));
97394
+ log(chalk.dim(" Secret: COPILOT_MCP_CODERIFTS_API_KEY (Agents secret) = CodeRifts API key"));
97395
+ log(chalk.dim(` ${summary.written.length} written, ${summary.skipped.length} skipped`));
97396
+ }
97397
+ if (doExit) process.exit(0);
97398
+ return { exitCode: 0, code: "OK", outDir, ...summary };
97399
+ }
97400
+ module2.exports = {
97401
+ runCopilotSetup,
97402
+ parseCopilotSetupArgs,
97403
+ COPILOT_MCP_FILES,
97404
+ COPILOT_MCP_PATHS,
97405
+ USAGE
97406
+ };
97407
+ }
97408
+ });
97409
+
97410
+ // src/commands/lock.js
97411
+ var require_lock = __commonJS({
97412
+ "src/commands/lock.js"(exports2, module2) {
97413
+ "use strict";
97414
+ var fs = require("fs");
97415
+ var path = require("path");
97416
+ var chalk = require_source();
97417
+ var { getApiKey } = require_config();
97418
+ var { cloudGetLock } = require_cloud();
97419
+ if (process.env.NO_COLOR) chalk.level = 0;
97420
+ var DEFAULT_OUT = "coderifts.lock";
97421
+ var EPHEMERAL_LOCK_KEYS = Object.freeze([
97422
+ "correlation_id",
97423
+ "generated_at",
97424
+ "request_correlation_id",
97425
+ "meta"
97426
+ ]);
97427
+ var COMMITTED_KEY_ORDER = Object.freeze([
97428
+ "lockfile_version",
97429
+ "repo",
97430
+ "provenance",
97431
+ "agents",
97432
+ "last_accepted_fingerprint",
97433
+ "decision_spec_version",
97434
+ "receipt_kind"
97435
+ ]);
97436
+ var USAGE = [
97437
+ "Usage: coderifts lock --repo owner/repo [--out coderifts.lock]",
97438
+ " or: coderifts lock owner/repo [--out path]",
97439
+ "",
97440
+ "Fetches the observed agent-contract lockfile (coderifts.lock v1) and writes it to disk.",
97441
+ "Requires a cloud API key (coderifts login or CODERIFTS_API_KEY).",
97442
+ "Observed-only: agents[] come from real usage observations; empty when none recorded.",
97443
+ "Written file is byte-stable for the same state (no correlation_id / generated_at)."
97444
+ ].join("\n");
97445
+ function isValidRepo(full) {
97446
+ const parts = String(full || "").split("/");
97447
+ if (parts.length !== 2) return false;
97448
+ const [owner, repo] = parts.map((p) => p.trim());
97449
+ return !!(owner && repo);
97450
+ }
97451
+ function toCommittedLockfile(doc) {
97452
+ const src = doc && typeof doc === "object" && !Array.isArray(doc) ? doc : {};
97453
+ const out = {};
97454
+ for (const key of COMMITTED_KEY_ORDER) {
97455
+ if (!Object.prototype.hasOwnProperty.call(src, key)) continue;
97456
+ if (EPHEMERAL_LOCK_KEYS.includes(key)) continue;
97457
+ out[key] = src[key];
97458
+ }
97459
+ for (const k of EPHEMERAL_LOCK_KEYS) {
97460
+ if (Object.prototype.hasOwnProperty.call(out, k)) delete out[k];
97461
+ }
97462
+ return out;
97463
+ }
97464
+ function renderLockfileJson(doc) {
97465
+ return `${JSON.stringify(doc, null, 2)}
97466
+ `;
97467
+ }
97468
+ async function runLock(options = {}, deps = {}) {
97469
+ const getKey = deps.getApiKey || getApiKey;
97470
+ const fetchLock = deps.cloudGetLock || cloudGetLock;
97471
+ const log = deps.log || console.log;
97472
+ const errLog = deps.errLog || console.error;
97473
+ const writeFile = deps.writeFile || ((p, c) => {
97474
+ fs.mkdirSync(path.dirname(path.resolve(p)), { recursive: true });
97475
+ fs.writeFileSync(p, c, "utf8");
97476
+ });
97477
+ const cwd = deps.cwd || process.cwd();
97478
+ const repo = options.repo != null && String(options.repo).trim() ? String(options.repo).trim() : null;
97479
+ if (!repo) {
97480
+ errLog(chalk.red("Error: missing repo"));
97481
+ errLog(USAGE);
97482
+ return { exitCode: 1, error: "missing_repo" };
97483
+ }
97484
+ if (!isValidRepo(repo)) {
97485
+ errLog(chalk.red("Error: repo must be in owner/repo form (e.g. coderifts/app)"));
97486
+ return { exitCode: 1, error: "invalid_repo" };
97487
+ }
97488
+ const apiKey = getKey();
97489
+ if (!apiKey) {
97490
+ errLog(chalk.red("Error: no API key. Run `coderifts login` or set CODERIFTS_API_KEY."));
97491
+ return { exitCode: 1, error: "missing_api_key" };
97492
+ }
97493
+ let doc;
97494
+ try {
97495
+ doc = await fetchLock(repo, apiKey);
97496
+ } catch (e) {
97497
+ const msg = e && e.message ? String(e.message) : "request failed";
97498
+ errLog(chalk.red(`Error: ${msg}`));
97499
+ if (e && e.code) errLog(chalk.dim(` (${e.code})`));
97500
+ return { exitCode: 1, error: msg };
97501
+ }
97502
+ if (!doc || typeof doc !== "object" || doc.lockfile_version == null) {
97503
+ errLog(chalk.red("Error: invalid lockfile response from API"));
97504
+ return { exitCode: 1, error: "invalid_response" };
97505
+ }
97506
+ const committed = toCommittedLockfile(doc);
97507
+ const body = renderLockfileJson(committed);
97508
+ const outRel = options.out != null && String(options.out).trim() ? String(options.out).trim() : DEFAULT_OUT;
97509
+ const outPath = path.isAbsolute(outRel) ? outRel : path.join(cwd, outRel);
97510
+ try {
97511
+ writeFile(outPath, body);
97512
+ } catch (e) {
97513
+ const msg = e && e.message ? String(e.message) : "write failed";
97514
+ errLog(chalk.red(`Error: failed to write ${outPath}: ${msg}`));
97515
+ return { exitCode: 1, error: msg };
97516
+ }
97517
+ if (options.json) {
97518
+ log(body.trimEnd());
97519
+ } else {
97520
+ const nAgents = Array.isArray(committed.agents) ? committed.agents.length : 0;
97521
+ const nOps = Array.isArray(committed.agents) ? committed.agents.reduce((n, a) => n + (a.operations && a.operations.length || 0), 0) : 0;
97522
+ log(chalk.bold("CodeRifts lock (observed)"));
97523
+ log(` repo: ${committed.repo || repo}`);
97524
+ log(` wrote: ${outPath}`);
97525
+ log(` agents: ${nAgents} operations: ${nOps}`);
97526
+ log(` fingerprint: ${committed.last_accepted_fingerprint || chalk.dim("(none)")}`);
97527
+ log(chalk.dim(" Observed-only; file is byte-stable (no correlation_id / generated_at)."));
97528
+ }
97529
+ return { exitCode: 0, doc, committed, outPath };
97530
+ }
97531
+ module2.exports = {
97532
+ runLock,
97533
+ renderLockfileJson,
97534
+ toCommittedLockfile,
97535
+ isValidRepo,
97536
+ DEFAULT_OUT,
97537
+ EPHEMERAL_LOCK_KEYS,
97538
+ COMMITTED_KEY_ORDER,
97539
+ USAGE
97540
+ };
97541
+ }
97542
+ });
97543
+
97232
97544
  // corpus/vectors-mcp-fpfn.json
97233
97545
  var require_vectors_mcp_fpfn = __commonJS({
97234
97546
  "corpus/vectors-mcp-fpfn.json"(exports2, module2) {
@@ -99180,6 +99492,20 @@ program.command("agent-setup").description("Write AGENTS.md / CLAUDE.md / Cursor
99180
99492
  const { runAgentSetup } = require_agent_setup();
99181
99493
  runAgentSetup(options, { exit: true });
99182
99494
  });
99495
+ program.command("copilot-setup").description("Write GitHub Copilot MCP configs (.vscode/mcp.json + cloud-agent paste JSON + docs)").option("--out <dir>", "Target directory (default: current working directory)").option("--check", "Exit 0 if on-disk files match embedded content; exit 1 on drift").option("--force", "Overwrite existing files (default: skip collisions)").action((options) => {
99496
+ const { runCopilotSetup } = require_copilot_setup();
99497
+ runCopilotSetup(options, { exit: true });
99498
+ });
99499
+ program.command("lock [repo]").description("Fetch the observed agent-contract lockfile (coderifts.lock v1) for a repo").option("--repo <owner/repo>", "Repository (owner/repo); also accepted as a positional argument").option("--out <path>", "Output path (default: coderifts.lock in cwd)").option("--json", "Print the lock document JSON to stdout (still writes --out)").action(async (repoPositional, options) => {
99500
+ const { runLock } = require_lock();
99501
+ const result = await runLock({
99502
+ ...options,
99503
+ repo: options.repo || repoPositional || null
99504
+ });
99505
+ if (result && typeof result.exitCode === "number") {
99506
+ process.exitCode = result.exitCode;
99507
+ }
99508
+ });
99183
99509
  var hookCmd = program.command("hook").description("Manage the CodeRifts pre-push Git hook");
99184
99510
  hookCmd.command("install").description("Install the CodeRifts pre-push hook in the current Git repo").action(() => {
99185
99511
  const { install } = require_hook();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "3.0.0",
3
+ "version": "3.1.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",