@cleartrip/frontguard 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,10 +6,10 @@ import * as tty from 'tty';
6
6
  import { WriteStream } from 'tty';
7
7
  import path4, { sep, normalize, delimiter, resolve, dirname } from 'path';
8
8
  import fs from 'fs/promises';
9
+ import { fileURLToPath, pathToFileURL } from 'url';
10
+ import { execFileSync, spawn } from 'child_process';
9
11
  import { createRequire } from 'module';
10
- import fs4 from 'fs';
11
- import { pathToFileURL } from 'url';
12
- import { spawn } from 'child_process';
12
+ import fs2 from 'fs';
13
13
  import { pipeline } from 'stream/promises';
14
14
  import { PassThrough } from 'stream';
15
15
  import fg from 'fast-glob';
@@ -2398,50 +2398,9 @@ async function runMain(cmd, opts = {}) {
2398
2398
  process.exit(1);
2399
2399
  }
2400
2400
  }
2401
- var WORKFLOW = `name: FrontGuard
2402
-
2403
- on:
2404
- pull_request:
2405
- types: [opened, synchronize, reopened]
2406
-
2407
- permissions:
2408
- contents: read
2409
- pull-requests: write
2410
-
2411
- concurrency:
2412
- group: frontguard-\${{ github.workflow }}-\${{ github.event.pull_request.number || github.ref }}
2413
- cancel-in-progress: true
2414
-
2415
- jobs:
2416
- review-brief:
2417
- runs-on: ubuntu-latest
2418
- steps:
2419
- - uses: actions/checkout@v4
2420
- with:
2421
- fetch-depth: 0
2422
-
2423
- - uses: actions/setup-node@v4
2424
- with:
2425
- node-version: 20
2426
-
2427
- - name: Install dependencies
2428
- run: |
2429
- if [ -f pnpm-lock.yaml ]; then
2430
- corepack enable
2431
- pnpm install --frozen-lockfile || pnpm install
2432
- elif [ -f yarn.lock ]; then
2433
- yarn install --frozen-lockfile || yarn install
2434
- elif [ -f package-lock.json ]; then
2435
- npm ci || npm install
2436
- else
2437
- npm install
2438
- fi
2439
-
2440
- - name: FrontGuard (Phase 1 \u2014 warn-only)
2441
- run: npx @cleartrip/frontguard run --ci
2442
- env:
2443
- GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
2444
- `;
2401
+ function packageRoot() {
2402
+ return path4.resolve(path4.dirname(fileURLToPath(import.meta.url)), "..");
2403
+ }
2445
2404
  var CONFIG = `import { defineConfig } from '@cleartrip/frontguard'
2446
2405
 
2447
2406
  export default defineConfig({
@@ -2496,27 +2455,35 @@ If **Yes**, list tools and what they touched (helps reviewers run a stricter fir
2496
2455
  ## AI assistance (optional detail)
2497
2456
  - [ ] I have reviewed every AI-suggested line for security, auth, and product correctness
2498
2457
  `;
2499
- async function ensureDir(dir) {
2500
- await fs.mkdir(dir, { recursive: true });
2501
- }
2502
2458
  async function initFrontGuard(cwd) {
2503
- const gh = path4.join(cwd, ".github", "workflows");
2504
- await ensureDir(gh);
2505
- const wfPath = path4.join(gh, "frontguard.yml");
2506
- await fs.writeFile(wfPath, WORKFLOW, "utf8");
2459
+ const root = packageRoot();
2460
+ const tplPath = path4.join(root, "templates", "bitbucket-pipelines.yml");
2461
+ const outPipeline = path4.join(cwd, "bitbucket-pipelines.frontguard.example.yml");
2462
+ try {
2463
+ await fs.access(outPipeline);
2464
+ } catch {
2465
+ try {
2466
+ const yml = await fs.readFile(tplPath, "utf8");
2467
+ await fs.writeFile(outPipeline, yml, "utf8");
2468
+ } catch {
2469
+ await fs.writeFile(
2470
+ outPipeline,
2471
+ "# Copy bitbucket-pipelines.yml from @cleartrip/frontguard/templates in node_modules\n",
2472
+ "utf8"
2473
+ );
2474
+ }
2475
+ }
2507
2476
  const cfgPath = path4.join(cwd, "frontguard.config.js");
2508
2477
  try {
2509
2478
  await fs.access(cfgPath);
2510
2479
  } catch {
2511
2480
  await fs.writeFile(cfgPath, CONFIG, "utf8");
2512
2481
  }
2513
- const tplRoot = path4.join(cwd, ".github");
2514
- await ensureDir(tplRoot);
2515
- const tplPath = path4.join(tplRoot, "pull_request_template.md");
2482
+ const tplPr = path4.join(cwd, "pull_request_template.md");
2516
2483
  try {
2517
- await fs.access(tplPath);
2484
+ await fs.access(tplPr);
2518
2485
  } catch {
2519
- await fs.writeFile(tplPath, PR_TEMPLATE, "utf8");
2486
+ await fs.writeFile(tplPr, PR_TEMPLATE, "utf8");
2520
2487
  }
2521
2488
  }
2522
2489
 
@@ -2654,110 +2621,86 @@ function parseAiDisclosure(body) {
2654
2621
  return { assisted, explicitNo, ambiguous };
2655
2622
  }
2656
2623
 
2657
- // src/ci/github.ts
2658
- async function readGithubEvent() {
2659
- const p2 = process.env.GITHUB_EVENT_PATH;
2660
- if (!p2) return null;
2661
- try {
2662
- const raw = await fs.readFile(p2, "utf8");
2663
- const payload = JSON.parse(raw);
2664
- const pr = payload.pull_request;
2665
- if (!pr) return null;
2666
- const files = (pr.files ?? []).map((f4) => f4.filename ?? "").filter(Boolean);
2667
- const body = pr.body ?? "";
2668
- const ai = parseAiDisclosure(body);
2669
- return {
2670
- number: pr.number ?? 0,
2671
- title: pr.title ?? "",
2672
- body,
2673
- baseRef: pr.base?.ref ?? "main",
2674
- headRef: pr.head?.ref ?? "",
2675
- additions: pr.additions ?? 0,
2676
- deletions: pr.deletions ?? 0,
2677
- changedFiles: pr.changed_files ?? files.length,
2678
- files,
2679
- aiAssisted: ai.assisted,
2680
- aiExplicitNo: ai.explicitNo,
2681
- aiDisclosureAmbiguous: ai.ambiguous
2682
- };
2683
- } catch {
2684
- return null;
2685
- }
2686
- }
2687
-
2688
- // src/lib/http-fetch.ts
2689
- function getFetch() {
2690
- const f4 = globalThis.fetch;
2691
- if (typeof f4 !== "function") {
2692
- throw new Error(
2693
- "FrontGuard needs Node.js 18+ (global fetch). Use NODE_VERSION / image >= 18 in CI."
2694
- );
2695
- }
2696
- return f4;
2697
- }
2698
-
2699
- // src/ci/pr-comment.ts
2700
- var MARKER = "<!-- frontguard:brief -->";
2701
- async function resolvePrNumber() {
2702
- const raw = process.env.FRONTGUARD_PR_NUMBER ?? process.env.PR_NUMBER;
2703
- const n3 = Number(raw);
2704
- if (Number.isFinite(n3) && n3 > 0) return n3;
2705
- const path17 = process.env.GITHUB_EVENT_PATH;
2706
- if (!path17) return null;
2707
- try {
2708
- const payload = JSON.parse(await fs.readFile(path17, "utf8"));
2709
- const num = payload.pull_request?.number;
2710
- return typeof num === "number" && num > 0 ? num : null;
2711
- } catch {
2712
- return null;
2624
+ // src/ci/pr-context.ts
2625
+ function gitTrimmed(cwd, args) {
2626
+ return execFileSync("git", ["-C", cwd, ...args], {
2627
+ encoding: "utf8",
2628
+ maxBuffer: 20 * 1024 * 1024
2629
+ }).trimEnd();
2630
+ }
2631
+ function resolveCompareRef(cwd, destBranch) {
2632
+ const safe = destBranch.trim();
2633
+ if (!safe || safe.startsWith("-") || safe.includes("..")) return null;
2634
+ const candidates = [safe, `origin/${safe}`];
2635
+ for (const c4 of candidates) {
2636
+ try {
2637
+ gitTrimmed(cwd, ["rev-parse", "--verify", `${c4}^{commit}`]);
2638
+ return c4;
2639
+ } catch {
2640
+ }
2713
2641
  }
2642
+ return null;
2714
2643
  }
2715
- async function upsertBriefComment(body) {
2716
- const token = process.env.GITHUB_TOKEN;
2717
- const repo = process.env.GITHUB_REPOSITORY;
2718
- if (!token || !repo) {
2719
- return;
2720
- }
2721
- const [owner, name] = repo.split("/");
2722
- if (!owner || !name) return;
2723
- const prNumber = await resolvePrNumber();
2724
- if (!prNumber) {
2725
- return;
2644
+ function parseNumstat(output) {
2645
+ let additions = 0;
2646
+ let deletions = 0;
2647
+ const files = [];
2648
+ for (const line of output.split("\n")) {
2649
+ const t3 = line.trim();
2650
+ if (!t3) continue;
2651
+ const tab = t3.indexOf(" ");
2652
+ if (tab < 0) continue;
2653
+ const tab2 = t3.indexOf(" ", tab + 1);
2654
+ if (tab2 < 0) continue;
2655
+ const aStr = t3.slice(0, tab);
2656
+ const dStr = t3.slice(tab + 1, tab2);
2657
+ const path17 = t3.slice(tab2 + 1);
2658
+ const a3 = aStr === "-" ? 0 : Number(aStr);
2659
+ const d3 = dStr === "-" ? 0 : Number(dStr);
2660
+ if (!Number.isFinite(a3) || !Number.isFinite(d3)) continue;
2661
+ additions += a3;
2662
+ deletions += d3;
2663
+ if (path17) files.push(path17);
2664
+ }
2665
+ return { additions, deletions, files };
2666
+ }
2667
+ function buildBitbucketPrContext(cwd) {
2668
+ const prId = process.env.BITBUCKET_PR_ID?.trim();
2669
+ if (!prId) return null;
2670
+ const dest = process.env.BITBUCKET_PR_DESTINATION_BRANCH?.trim() || "main";
2671
+ const body = process.env.FRONTGUARD_PR_BODY?.trim() ?? "";
2672
+ const headRef = process.env.BITBUCKET_PR_SOURCE_BRANCH?.trim() ?? "";
2673
+ const title = process.env.BITBUCKET_PR_TITLE?.trim() ?? "";
2674
+ let additions = 0;
2675
+ let deletions = 0;
2676
+ let files = [];
2677
+ const destRef = resolveCompareRef(cwd, dest);
2678
+ if (destRef) {
2679
+ try {
2680
+ const raw = gitTrimmed(cwd, ["diff", `${destRef}...HEAD`, "--numstat"]);
2681
+ const stat = parseNumstat(raw);
2682
+ additions = stat.additions;
2683
+ deletions = stat.deletions;
2684
+ files = stat.files;
2685
+ } catch {
2686
+ }
2726
2687
  }
2727
- const apiBase = process.env.GITHUB_API_URL ?? "https://api.github.com";
2728
- const headers = {
2729
- authorization: `Bearer ${token}`,
2730
- accept: "application/vnd.github+json",
2731
- "x-github-api-version": "2022-11-28",
2732
- "content-type": "application/json"
2688
+ const ai = parseAiDisclosure(body);
2689
+ return {
2690
+ number: Number.parseInt(prId, 10) || 0,
2691
+ title,
2692
+ body,
2693
+ baseRef: dest,
2694
+ headRef,
2695
+ additions,
2696
+ deletions,
2697
+ changedFiles: files.length,
2698
+ files,
2699
+ ...ai
2733
2700
  };
2734
- const prefixed = `${MARKER}
2735
- ${body}`;
2736
- const listUrl = `${apiBase}/repos/${owner}/${name}/issues/${prNumber}/comments?per_page=100`;
2737
- const fetch = getFetch();
2738
- const listRes = await fetch(listUrl, { headers });
2739
- if (!listRes.ok) {
2740
- return;
2741
- }
2742
- const comments = await listRes.json();
2743
- const existing = comments.find(
2744
- (c4) => typeof c4.body === "string" && c4.body.includes(MARKER)
2745
- );
2746
- if (existing) {
2747
- const patchUrl = `${apiBase}/repos/${owner}/${name}/issues/comments/${existing.id}`;
2748
- await fetch(patchUrl, {
2749
- method: "PATCH",
2750
- headers,
2751
- body: JSON.stringify({ body: prefixed })
2752
- });
2753
- return;
2754
- }
2755
- const postUrl = `${apiBase}/repos/${owner}/${name}/issues/${prNumber}/comments`;
2756
- await fetch(postUrl, {
2757
- method: "POST",
2758
- headers,
2759
- body: JSON.stringify({ body: prefixed })
2760
- });
2701
+ }
2702
+ function readPrContext(cwd) {
2703
+ return buildBitbucketPrContext(cwd);
2761
2704
  }
2762
2705
 
2763
2706
  // node_modules/defu/dist/defu.mjs
@@ -2937,7 +2880,7 @@ async function loadConfig(cwd) {
2937
2880
  let userFile = null;
2938
2881
  for (const name of CONFIG_NAMES) {
2939
2882
  const full = path4.join(cwd, name);
2940
- if (!fs4.existsSync(full)) continue;
2883
+ if (!fs2.existsSync(full)) continue;
2941
2884
  try {
2942
2885
  const mod = await importConfig(full);
2943
2886
  userFile = normalizeExport(mod);
@@ -4084,7 +4027,7 @@ function runPrHygiene(config, pr) {
4084
4027
  checkId: "pr-hygiene",
4085
4028
  findings: [],
4086
4029
  durationMs: Math.round(performance.now() - t0),
4087
- skipped: "not running in GitHub pull_request context"
4030
+ skipped: "no PR context (run in a Bitbucket PR pipeline with BITBUCKET_PR_ID, or set FRONTGUARD_PR_BODY for description checks)"
4088
4031
  };
4089
4032
  }
4090
4033
  const findings = [];
@@ -4178,7 +4121,7 @@ function runPrSize(config, pr) {
4178
4121
  checkId: "pr-size",
4179
4122
  findings: [],
4180
4123
  durationMs: Math.round(performance.now() - t0),
4181
- skipped: "not running in GitHub pull_request context"
4124
+ skipped: "no PR context (run in a Bitbucket pull-request pipeline so BITBUCKET_PR_* and git diff are available)"
4182
4125
  };
4183
4126
  }
4184
4127
  const findings = [];
@@ -4248,13 +4191,18 @@ async function gitDiffForReview(cwd, baseRef, maxChars) {
4248
4191
  }
4249
4192
  }
4250
4193
  async function resolveDiffBaseRef(cwd, fallback) {
4251
- const gh = process.env.GITHUB_BASE_REF;
4252
- if (gh) {
4253
- const origin = `origin/${gh}`;
4194
+ const bb = process.env.BITBUCKET_PR_DESTINATION_BRANCH?.trim();
4195
+ if (bb) {
4196
+ const origin = `origin/${bb}`;
4254
4197
  try {
4255
4198
  await W2("git", ["rev-parse", "--verify", origin], { nodeOptions: { cwd } });
4256
4199
  return origin;
4257
4200
  } catch {
4201
+ try {
4202
+ await W2("git", ["rev-parse", "--verify", bb], { nodeOptions: { cwd } });
4203
+ return bb;
4204
+ } catch {
4205
+ }
4258
4206
  }
4259
4207
  }
4260
4208
  try {
@@ -4533,6 +4481,39 @@ async function gitOkQuick(cwd) {
4533
4481
  function tokenizeCommand(cmd) {
4534
4482
  return cmd.trim().split(/\s+/).map((t3) => t3.trim()).filter(Boolean);
4535
4483
  }
4484
+ function npmScriptFromBuildCommand(cmd) {
4485
+ const t3 = cmd.trim();
4486
+ if (/^yarn\s+build\b/i.test(t3)) return "build";
4487
+ const np = /^(?:npm|pnpm)\s+run\s+(\S+)/i.exec(t3);
4488
+ if (np?.[1]) return np[1];
4489
+ const yr = /^yarn\s+run\s+(\S+)/i.exec(t3);
4490
+ if (yr?.[1]) return yr[1];
4491
+ return null;
4492
+ }
4493
+ async function bundleBuildPrecheck(cwd, buildCommand) {
4494
+ const script = npmScriptFromBuildCommand(buildCommand);
4495
+ if (!script) return { run: true };
4496
+ let scripts;
4497
+ try {
4498
+ const raw = await fs.readFile(path4.join(cwd, "package.json"), "utf8");
4499
+ const pkg = JSON.parse(raw);
4500
+ scripts = pkg.scripts;
4501
+ } catch {
4502
+ return {
4503
+ run: false,
4504
+ message: "Skipped bundle build \u2014 no readable package.json",
4505
+ detail: "Set checks.bundle.buildCommand to your real build (e.g. `vite build`), set checks.bundle.runBuild to false, or add a package.json with the matching script."
4506
+ };
4507
+ }
4508
+ if (!scripts?.[script]) {
4509
+ return {
4510
+ run: false,
4511
+ message: `Skipped bundle build \u2014 no scripts.${script} in package.json`,
4512
+ detail: "The bundle check runs a production build, then sums file sizes under checks.bundle.measureGlobs (dist/, build/static/, .next/static/, etc.). Libraries and non-web repos often have no `build` script \u2014 set checks.bundle.runBuild to false and optionally tune measureGlobs, or set checks.bundle.buildCommand to whatever produces your artifacts (e.g. `pnpm run compile`, `npx vite build`)."
4513
+ };
4514
+ }
4515
+ return { run: true };
4516
+ }
4536
4517
  async function runBundle(cwd, config, stack) {
4537
4518
  const t0 = performance.now();
4538
4519
  const cfg = config.checks.bundle;
@@ -4552,6 +4533,7 @@ async function runBundle(cwd, config, stack) {
4552
4533
  skipped: "skipped for React Native (configure web artifacts if needed)"
4553
4534
  };
4554
4535
  }
4536
+ const preFindings = [];
4555
4537
  if (cfg.runBuild) {
4556
4538
  const parts = tokenizeCommand(cfg.buildCommand);
4557
4539
  if (parts.length === 0) {
@@ -4567,21 +4549,31 @@ async function runBundle(cwd, config, stack) {
4567
4549
  durationMs: Math.round(performance.now() - t0)
4568
4550
  };
4569
4551
  }
4570
- const [bin, ...args] = parts;
4571
- const res = await W2(bin, args, { nodeOptions: { cwd } });
4572
- if ((res.exitCode ?? 0) !== 0) {
4573
- return {
4574
- checkId: "bundle",
4575
- findings: [
4576
- {
4577
- id: "bundle-build",
4578
- severity: gateSeverity4(cfg.gate),
4579
- message: "Build command failed \u2014 cannot measure bundle",
4580
- detail: [res.stdout, res.stderr].filter(Boolean).join("\n").slice(0, 8e3)
4581
- }
4582
- ],
4583
- durationMs: Math.round(performance.now() - t0)
4584
- };
4552
+ const pre = await bundleBuildPrecheck(cwd, cfg.buildCommand);
4553
+ if (!pre.run) {
4554
+ preFindings.push({
4555
+ id: "bundle-build-skipped",
4556
+ severity: "info",
4557
+ message: pre.message,
4558
+ detail: pre.detail
4559
+ });
4560
+ } else {
4561
+ const [bin, ...args] = parts;
4562
+ const res = await W2(bin, args, { nodeOptions: { cwd } });
4563
+ if ((res.exitCode ?? 0) !== 0) {
4564
+ return {
4565
+ checkId: "bundle",
4566
+ findings: [
4567
+ {
4568
+ id: "bundle-build",
4569
+ severity: gateSeverity4(cfg.gate),
4570
+ message: "Build command failed \u2014 cannot measure bundle",
4571
+ detail: [res.stdout, res.stderr].filter(Boolean).join("\n").slice(0, 8e3)
4572
+ }
4573
+ ],
4574
+ durationMs: Math.round(performance.now() - t0)
4575
+ };
4576
+ }
4585
4577
  }
4586
4578
  }
4587
4579
  const total = await sumGlobBytes(cwd, cfg.measureGlobs);
@@ -4589,6 +4581,7 @@ async function runBundle(cwd, config, stack) {
4589
4581
  return {
4590
4582
  checkId: "bundle",
4591
4583
  findings: [
4584
+ ...preFindings,
4592
4585
  {
4593
4586
  id: "bundle-empty",
4594
4587
  severity: "info",
@@ -4600,7 +4593,7 @@ async function runBundle(cwd, config, stack) {
4600
4593
  }
4601
4594
  const baseRef = await gitOkQuick(cwd) ? await resolveDiffBaseRef(cwd, cfg.baselineRef) : null;
4602
4595
  const baseline = await readBaseline(cwd, cfg.baselinePath, baseRef);
4603
- const findings = [];
4596
+ const findings = [...preFindings];
4604
4597
  const infoLines = [
4605
4598
  `Measured ${total} bytes (${(total / 1024 / 1024).toFixed(2)} MiB)`,
4606
4599
  baseline ? `Baseline from \`${cfg.baselinePath}\`: ${baseline.totalBytes} bytes` : `No baseline at \`${cfg.baselinePath}\` (commit a baseline JSON to compare)`
@@ -5079,8 +5072,8 @@ function buildHtmlReport(p2) {
5079
5072
  } else {
5080
5073
  for (const cid of checkOrder) {
5081
5074
  const group = sortFindings(cwd, byCheck.get(cid));
5082
- warningsHtml += `<h3 class="grp">${escapeHtml(cid)} <span class="count">(${group.length})</span></h3>`;
5083
- warningsHtml += group.map(({ r: r4, f: f4 }) => renderFindingCard(cwd, r4, f4)).join("\n");
5075
+ const cards = group.map(({ r: r4, f: f4 }) => renderFindingCard(cwd, r4, f4)).join("\n");
5076
+ warningsHtml += `<details class="warn-check"><summary>${escapeHtml(cid)} <span class="count">(${group.length})</span></summary><div class="details-body warn-check-body">${cards}</div></details>`;
5084
5077
  }
5085
5078
  }
5086
5079
  const infoHtml = infoItems.length === 0 ? '<p class="muted">No info notes.</p>' : infoItems.map(({ r: r4, f: f4 }) => renderFindingCard(cwd, r4, f4)).join("\n");
@@ -5114,8 +5107,11 @@ function buildHtmlReport(p2) {
5114
5107
  }
5115
5108
  h1 { font-size: 1.5rem; margin: 0 0 1rem; letter-spacing: -0.02em; }
5116
5109
  h2 { font-size: 1.15rem; margin: 2rem 0 0.75rem; color: var(--accent); border-bottom: 1px solid var(--border); padding-bottom: 0.35rem; }
5117
- h3.grp { margin: 1.5rem 0 0.5rem; font-size: 1rem; color: var(--warn); }
5118
- h3.grp .count { color: var(--muted); font-weight: normal; }
5110
+ details.warn-check { margin-bottom: 0.45rem; }
5111
+ details.warn-check:last-child { margin-bottom: 0; }
5112
+ details.warn-check > summary { color: var(--warn); font-size: 0.95rem; }
5113
+ details.warn-check .count { color: var(--muted); font-weight: normal; }
5114
+ .warn-check-body { padding-top: 0.35rem; }
5119
5115
  h4 { font-size: 0.95rem; margin: 0 0 0.5rem; font-weight: 600; }
5120
5116
  h5 { font-size: 0.8rem; margin: 0 0 0.35rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--accent); }
5121
5117
  .badges { margin-bottom: 1.25rem; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
@@ -5605,6 +5601,17 @@ function formatConsole(p2) {
5605
5601
  return lines.join("\n");
5606
5602
  }
5607
5603
 
5604
+ // src/lib/http-fetch.ts
5605
+ function getFetch() {
5606
+ const f4 = globalThis.fetch;
5607
+ if (typeof f4 !== "function") {
5608
+ throw new Error(
5609
+ "FrontGuard needs Node.js 18+ (global fetch). Use NODE_VERSION / image >= 18 in CI."
5610
+ );
5611
+ }
5612
+ return f4;
5613
+ }
5614
+
5608
5615
  // src/llm/ollama.ts
5609
5616
  async function callOllamaChat(opts) {
5610
5617
  const fetch = getFetch();
@@ -5942,7 +5949,7 @@ async function runFrontGuard(opts) {
5942
5949
  const config = await loadConfig(opts.cwd);
5943
5950
  const mode = opts.enforce ? "enforce" : config.mode;
5944
5951
  const stack = await detectStack(opts.cwd);
5945
- const pr = await readGithubEvent();
5952
+ const pr = readPrContext(opts.cwd);
5946
5953
  const restrictFiles = pr?.files?.length ? pr.files : null;
5947
5954
  const [
5948
5955
  eslint,
@@ -6031,9 +6038,6 @@ FrontGuard: wrote Bitbucket PR comment text to ${abs} (${snippet.length} bytes).
6031
6038
  g.stdout.write(report.consoleText + "\n\n");
6032
6039
  g.stdout.write(report.markdown + "\n");
6033
6040
  }
6034
- if (opts.ci && g.env.GITHUB_TOKEN) {
6035
- await upsertBriefComment(report.markdown);
6036
- }
6037
6041
  const hasBlock = results.some((r4) => r4.findings.some((f4) => f4.severity === "block"));
6038
6042
  g.exitCode = mode === "enforce" && hasBlock ? 1 : 0;
6039
6043
  }
@@ -6042,13 +6046,13 @@ FrontGuard: wrote Bitbucket PR comment text to ${abs} (${snippet.length} bytes).
6042
6046
  var init2 = defineCommand({
6043
6047
  meta: {
6044
6048
  name: "init",
6045
- description: "Add workflow, PR template, and frontguard.config.js"
6049
+ description: "Add Bitbucket pipeline example, pull_request_template.md, and frontguard.config.js"
6046
6050
  },
6047
6051
  run: async () => {
6048
6052
  const cwd = g.cwd();
6049
6053
  await initFrontGuard(cwd);
6050
6054
  g.stdout.write(
6051
- "FrontGuard initialized.\n\nNext: add the package as a devDependency so CI matches local runs:\n npm install -D @cleartrip/frontguard\n yarn add -D @cleartrip/frontguard\n"
6055
+ "FrontGuard initialized for Bitbucket.\n\n \u2022 bitbucket-pipelines.frontguard.example.yml \u2014 merge into your pipeline\n \u2022 pull_request_template.md \u2014 PR description template (Bitbucket Cloud)\n \u2022 frontguard.config.js (if missing)\n\nAdd the package as a devDependency so CI matches local runs:\n npm install -D @cleartrip/frontguard\n yarn add -D @cleartrip/frontguard\n"
6052
6056
  );
6053
6057
  }
6054
6058
  });
@@ -6058,11 +6062,6 @@ var run = defineCommand({
6058
6062
  description: "Run checks and print the review brief"
6059
6063
  },
6060
6064
  args: {
6061
- ci: {
6062
- type: "boolean",
6063
- description: "Upsert PR comment when GITHUB_TOKEN is available",
6064
- default: false
6065
- },
6066
6065
  markdown: {
6067
6066
  type: "boolean",
6068
6067
  description: "Print markdown only",
@@ -6089,7 +6088,6 @@ var run = defineCommand({
6089
6088
  run: async ({ args }) => {
6090
6089
  await runFrontGuard({
6091
6090
  cwd: g.cwd(),
6092
- ci: Boolean(args.ci),
6093
6091
  markdown: Boolean(args.markdown),
6094
6092
  enforce: Boolean(args.enforce),
6095
6093
  append: typeof args.append === "string" ? args.append : null,