@methiu/raigal 1.0.1 → 1.0.2

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
@@ -108,7 +108,28 @@ const getProviderStatuses = () => PROVIDERS.map((provider) => {
108
108
  };
109
109
  });
110
110
  const resolveProvider = (selection, statuses = getProviderStatuses()) => {
111
- if (selection !== "auto") return statuses.find((status) => status.provider.id === selection) ?? null;
111
+ if (selection !== "auto") {
112
+ const found = statuses.find((status) => status.provider.id === selection);
113
+ if (found) return found;
114
+ if (commandExists(selection)) return {
115
+ provider: {
116
+ id: selection,
117
+ label: selection,
118
+ bin: selection,
119
+ loginCommand: {
120
+ command: selection,
121
+ args: ["login"]
122
+ },
123
+ loginHint: `Run \`${selection} login\`.`,
124
+ buildArgs: (prompt) => [prompt]
125
+ },
126
+ installed: true,
127
+ authenticated: true,
128
+ version: readVersion(selection),
129
+ authHint: null
130
+ };
131
+ return null;
132
+ }
112
133
  return statuses.find((status) => status.installed && status.authenticated !== false) ?? statuses.find((status) => status.installed) ?? null;
113
134
  };
114
135
  //#endregion
@@ -863,7 +884,7 @@ const createLogger = (deps = {}) => {
863
884
  const log = createLogger();
864
885
  //#endregion
865
886
  //#region src/version.ts
866
- const APP_VERSION = "1.0.1";
887
+ const APP_VERSION = "1.0.2";
867
888
  //#endregion
868
889
  //#region src/commands/agent-connect.ts
869
890
  const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
@@ -1058,7 +1079,8 @@ const Finding = z.object({
1058
1079
  severity: Severity,
1059
1080
  path: z.string().max(500),
1060
1081
  line: z.number().int().nonnegative(),
1061
- fingerprint: z.string().length(32)
1082
+ fingerprint: z.string().length(32),
1083
+ message: z.string().max(500).optional()
1062
1084
  });
1063
1085
  const RepoRef = z.object({
1064
1086
  provider: z.literal("github"),
@@ -1073,12 +1095,21 @@ const Step = z.object({
1073
1095
  ms: z.number().int().nonnegative(),
1074
1096
  counts: z.record(z.string(), z.number()).optional()
1075
1097
  });
1098
+ const AgentTelemetry = z.object({
1099
+ provider: z.string().max(80),
1100
+ before_score: z.number().min(0).max(100).nullable(),
1101
+ after_score: z.number().min(0).max(100).nullable(),
1102
+ pr_url: z.string().url().max(500).optional(),
1103
+ patch_diff: z.string().max(2e4).optional(),
1104
+ duration_ms: z.number().int().nonnegative().optional()
1105
+ });
1076
1106
  const Report = z.object({
1077
1107
  score: z.number().min(0).max(100).nullable(),
1078
1108
  scoreable: z.boolean(),
1079
1109
  engine_scores: z.record(z.string(), z.number()).optional(),
1080
1110
  files_scanned: z.number().int().nonnegative(),
1081
- findings: z.array(Finding).max(5e3)
1111
+ findings: z.array(Finding).max(5e3),
1112
+ agent_telemetry: AgentTelemetry.optional()
1082
1113
  });
1083
1114
  const Run = z.object({
1084
1115
  run_id: z.uuid(),
@@ -1095,7 +1126,13 @@ const Run = z.object({
1095
1126
  branch: z.string().max(200).optional(),
1096
1127
  head_sha: z.string().max(64).optional(),
1097
1128
  base_ref: z.string().max(200).optional(),
1098
- pr_number: z.number().int().optional()
1129
+ pr_number: z.number().int().optional(),
1130
+ diff_summary: z.object({
1131
+ files_changed: z.number().int().nonnegative(),
1132
+ additions: z.number().int().nonnegative(),
1133
+ deletions: z.number().int().nonnegative()
1134
+ }).optional(),
1135
+ diff_preview: z.string().max(1e4).optional()
1099
1136
  }),
1100
1137
  ci: z.object({
1101
1138
  provider: z.string().max(40),
@@ -1111,7 +1148,8 @@ const Run = z.object({
1111
1148
  ended_at: z.iso.datetime(),
1112
1149
  exit_code: z.number().int(),
1113
1150
  steps: z.array(Step).max(200),
1114
- report: Report.optional()
1151
+ report: Report.optional(),
1152
+ agent_telemetry: AgentTelemetry.optional()
1115
1153
  });
1116
1154
  const Policy = z.object({
1117
1155
  version: z.number().int(),
@@ -1345,7 +1383,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
1345
1383
  const claims = parsed.data;
1346
1384
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
1347
1385
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
1348
- if (!isVersionAtLeast("1.0.1", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
1386
+ if (!isVersionAtLeast("1.0.2", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
1349
1387
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
1350
1388
  const grace = options.allowGraceSeconds ?? 0;
1351
1389
  if (now > claims.exp + grace) {
@@ -2575,6 +2613,385 @@ var AgentTui = class {
2575
2613
  }
2576
2614
  };
2577
2615
  //#endregion
2616
+ //#region src/cloud/context.ts
2617
+ const execGit = (args, cwd) => {
2618
+ try {
2619
+ const res = spawnSync("git", args, {
2620
+ cwd: path.resolve(cwd),
2621
+ encoding: "utf-8",
2622
+ stdio: [
2623
+ "ignore",
2624
+ "pipe",
2625
+ "ignore"
2626
+ ]
2627
+ });
2628
+ if (res.error || res.status !== 0) return void 0;
2629
+ return res.stdout.trim() || void 0;
2630
+ } catch {
2631
+ return;
2632
+ }
2633
+ };
2634
+ const detectDefaultBranch = (directory) => {
2635
+ try {
2636
+ const out = execGit(["symbolic-ref", "refs/remotes/origin/HEAD"], directory);
2637
+ if (out && out.startsWith("refs/remotes/origin/")) return out.replace("refs/remotes/origin/", "").trim();
2638
+ } catch {}
2639
+ return "main";
2640
+ };
2641
+ const detectGitDiff = (directory, baseRef) => {
2642
+ const targetBase = baseRef || detectDefaultBranch(directory);
2643
+ try {
2644
+ let shortstat = execGit([
2645
+ "diff",
2646
+ "--shortstat",
2647
+ `${targetBase}...HEAD`
2648
+ ], directory);
2649
+ if (!shortstat) shortstat = execGit([
2650
+ "diff",
2651
+ "--shortstat",
2652
+ `origin/${targetBase}...HEAD`
2653
+ ], directory);
2654
+ if (!shortstat) shortstat = execGit([
2655
+ "diff",
2656
+ "--shortstat",
2657
+ "HEAD~1"
2658
+ ], directory);
2659
+ let diffSummary;
2660
+ if (shortstat) {
2661
+ const filesMatch = shortstat.match(/(\d+)\s+files?\s+changed/);
2662
+ const addMatch = shortstat.match(/(\d+)\s+insertions?\s*\(\+\)/);
2663
+ const delMatch = shortstat.match(/(\d+)\s+deletions?\s*\(-\)/);
2664
+ diffSummary = {
2665
+ files_changed: filesMatch ? Number.parseInt(filesMatch[1], 10) : 1,
2666
+ additions: addMatch ? Number.parseInt(addMatch[1], 10) : 0,
2667
+ deletions: delMatch ? Number.parseInt(delMatch[1], 10) : 0
2668
+ };
2669
+ }
2670
+ let preview = execGit([
2671
+ "diff",
2672
+ "-U2",
2673
+ "--no-color",
2674
+ `${targetBase}...HEAD`
2675
+ ], directory);
2676
+ if (!preview) preview = execGit([
2677
+ "diff",
2678
+ "-U2",
2679
+ "--no-color",
2680
+ `origin/${targetBase}...HEAD`
2681
+ ], directory);
2682
+ if (!preview) preview = execGit([
2683
+ "diff",
2684
+ "-U2",
2685
+ "--no-color",
2686
+ "HEAD~1"
2687
+ ], directory);
2688
+ let diffPreview;
2689
+ if (preview) diffPreview = preview.split("\n").slice(0, 40).join("\n").slice(0, 1e4);
2690
+ return {
2691
+ diffSummary,
2692
+ diffPreview
2693
+ };
2694
+ } catch {
2695
+ return {};
2696
+ }
2697
+ };
2698
+ const getRootCommitHash = (directory) => {
2699
+ const hash = execGit([
2700
+ "rev-list",
2701
+ "--max-parents=0",
2702
+ "HEAD"
2703
+ ], directory);
2704
+ if (!hash) return void 0;
2705
+ const firstLine = hash.split("\n")[0]?.trim();
2706
+ return firstLine && /^[0-9a-f]{40,64}$/i.test(firstLine) ? firstLine : void 0;
2707
+ };
2708
+ const detectRepoRef = (directory, ownerId) => {
2709
+ const detected = detectLocalRepoOwner(directory);
2710
+ if (!detected.owner || !detected.repo) return null;
2711
+ return {
2712
+ provider: "github",
2713
+ slug: `${detected.owner}/${detected.repo}`.toLowerCase(),
2714
+ owner_id: ownerId,
2715
+ root_commit_hash: getRootCommitHash(directory)
2716
+ };
2717
+ };
2718
+ const readGitHubEventPayload = () => {
2719
+ const eventPath = process.env.GITHUB_EVENT_PATH;
2720
+ if (!eventPath || !fs.existsSync(eventPath)) return null;
2721
+ try {
2722
+ const content = fs.readFileSync(eventPath, "utf-8");
2723
+ return JSON.parse(content);
2724
+ } catch {
2725
+ return null;
2726
+ }
2727
+ };
2728
+ const detectGitContext = (directory) => {
2729
+ const eventPayload = readGitHubEventPayload();
2730
+ const pr = eventPayload?.pull_request;
2731
+ let headSha = pr?.head?.sha;
2732
+ if (!headSha) headSha = process.env.GITHUB_SHA || execGit(["rev-parse", "HEAD"], directory);
2733
+ let branch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;
2734
+ if (!branch) {
2735
+ branch = execGit([
2736
+ "rev-parse",
2737
+ "--abbrev-ref",
2738
+ "HEAD"
2739
+ ], directory);
2740
+ if (branch === "HEAD") branch = void 0;
2741
+ }
2742
+ let baseRef = pr?.base?.ref || process.env.GITHUB_BASE_REF;
2743
+ if (!baseRef) baseRef = void 0;
2744
+ let prNumber;
2745
+ if (typeof pr?.number === "number") prNumber = pr.number;
2746
+ else if (typeof eventPayload?.number === "number") prNumber = eventPayload.number;
2747
+ else if (process.env.GITHUB_REF) {
2748
+ const match = process.env.GITHUB_REF.match(/^refs\/pull\/(\d+)\//);
2749
+ if (match && match[1]) prNumber = Number.parseInt(match[1], 10);
2750
+ }
2751
+ const result = {};
2752
+ if (branch) result.branch = branch;
2753
+ if (headSha) result.head_sha = headSha;
2754
+ if (baseRef) result.base_ref = baseRef;
2755
+ if (prNumber !== void 0 && !Number.isNaN(prNumber)) result.pr_number = prNumber;
2756
+ const { diffSummary, diffPreview } = detectGitDiff(directory, baseRef);
2757
+ if (diffSummary) result.diff_summary = diffSummary;
2758
+ if (diffPreview) result.diff_preview = diffPreview;
2759
+ return result;
2760
+ };
2761
+ const detectCiContext = () => {
2762
+ if (process.env.GITHUB_ACTIONS === "true" || process.env.GITHUB_ACTIONS === "1") {
2763
+ const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
2764
+ const repo = process.env.GITHUB_REPOSITORY;
2765
+ const runId = process.env.GITHUB_RUN_ID;
2766
+ let runUrl;
2767
+ if (repo && runId) {
2768
+ const attempt = process.env.GITHUB_RUN_ATTEMPT;
2769
+ runUrl = `${serverUrl}/${repo}/actions/runs/${runId}${attempt ? `/attempts/${attempt}` : ""}`;
2770
+ }
2771
+ return {
2772
+ provider: "github-actions",
2773
+ ...runUrl ? { run_url: runUrl } : {}
2774
+ };
2775
+ }
2776
+ if (process.env.CI === "true" || process.env.CI === "1") return { provider: process.env.GITLAB_CI && "gitlab-ci" || process.env.BITBUCKET_BUILD_NUMBER && "bitbucket-pipelines" || process.env.CIRCLECI && "circleci" || process.env.BUILDKITE && "buildkite" || process.env.TRAVIS && "travis-ci" || "generic-ci" };
2777
+ };
2778
+ //#endregion
2779
+ //#region src/cloud/outbox.ts
2780
+ const MAX_OUTBOX_BYTES = 5 * 1024 * 1024;
2781
+ const getMaxOutboxBytes = () => {
2782
+ const override = process.env.RAIGAL_MAX_OUTBOX_BYTES;
2783
+ if (override) {
2784
+ const parsed = Number.parseInt(override, 10);
2785
+ if (!Number.isNaN(parsed) && parsed > 0) return parsed;
2786
+ }
2787
+ return MAX_OUTBOX_BYTES;
2788
+ };
2789
+ const loadOutboxRuns = () => {
2790
+ const outboxPath = getOutboxPath();
2791
+ if (!fs.existsSync(outboxPath)) return [];
2792
+ try {
2793
+ const lines = fs.readFileSync(outboxPath, "utf-8").split("\n").filter((l) => l.trim().length > 0);
2794
+ const runs = [];
2795
+ for (const line of lines) try {
2796
+ const parsed = JSON.parse(line);
2797
+ const valid = Run.safeParse(parsed);
2798
+ if (valid.success) runs.push(valid.data);
2799
+ } catch {}
2800
+ return runs;
2801
+ } catch {
2802
+ return [];
2803
+ }
2804
+ };
2805
+ const writeRunsToOutbox = (runs) => {
2806
+ const outboxPath = getOutboxPath();
2807
+ const dir = path.dirname(outboxPath);
2808
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, {
2809
+ recursive: true,
2810
+ mode: 448
2811
+ });
2812
+ let lines = runs.map((r) => JSON.stringify(r));
2813
+ let text = lines.length > 0 ? `${lines.join("\n")}\n` : "";
2814
+ const maxBytes = getMaxOutboxBytes();
2815
+ while (Buffer.byteLength(text, "utf-8") > maxBytes && lines.length > 1) {
2816
+ lines.shift();
2817
+ text = `${lines.join("\n")}\n`;
2818
+ }
2819
+ const temp = `${outboxPath}.${Date.now()}.tmp`;
2820
+ fs.writeFileSync(temp, text, { mode: 384 });
2821
+ try {
2822
+ fs.renameSync(temp, outboxPath);
2823
+ } catch {
2824
+ fs.rmSync(temp, { force: true });
2825
+ fs.writeFileSync(outboxPath, text, { mode: 384 });
2826
+ }
2827
+ };
2828
+ const enqueueRun = (run) => {
2829
+ const existing = loadOutboxRuns();
2830
+ existing.push(run);
2831
+ writeRunsToOutbox(existing);
2832
+ };
2833
+ const getLastRecordedRun = () => {
2834
+ const runs = loadOutboxRuns();
2835
+ return runs.length > 0 ? runs[runs.length - 1] ?? null : null;
2836
+ };
2837
+ const flushOutbox = async (token) => {
2838
+ const runs = loadOutboxRuns();
2839
+ if (runs.length === 0) return {
2840
+ sent: 0,
2841
+ failed: 0,
2842
+ remaining: 0
2843
+ };
2844
+ let sentCount = 0;
2845
+ let failCount = 0;
2846
+ const remainingRuns = [];
2847
+ for (let i = 0; i < runs.length; i++) {
2848
+ const run = runs[i];
2849
+ try {
2850
+ await requestApi("/v1/runs", {
2851
+ method: "POST",
2852
+ token,
2853
+ body: run,
2854
+ timeoutMs: 4e3
2855
+ });
2856
+ sentCount++;
2857
+ } catch (err) {
2858
+ if (err instanceof ApiError) {
2859
+ if ([
2860
+ 400,
2861
+ 401,
2862
+ 403,
2863
+ 413,
2864
+ 422
2865
+ ].includes(err.status)) {
2866
+ failCount++;
2867
+ continue;
2868
+ }
2869
+ if (err.status === 409) {
2870
+ sentCount++;
2871
+ continue;
2872
+ }
2873
+ }
2874
+ failCount++;
2875
+ remainingRuns.push(...runs.slice(i));
2876
+ break;
2877
+ }
2878
+ }
2879
+ writeRunsToOutbox(remainingRuns);
2880
+ return {
2881
+ sent: sentCount,
2882
+ failed: failCount,
2883
+ remaining: remainingRuns.length
2884
+ };
2885
+ };
2886
+ //#endregion
2887
+ //#region src/cloud/recorder.ts
2888
+ var CloudRunRecorder = class {
2889
+ runId;
2890
+ command;
2891
+ directory;
2892
+ flags;
2893
+ startedAt;
2894
+ steps = [];
2895
+ report;
2896
+ policyMeta;
2897
+ gitContextOverride;
2898
+ agentTelemetry;
2899
+ seq = 0;
2900
+ finished = false;
2901
+ constructor(command, directory, flags = []) {
2902
+ this.runId = randomUUID();
2903
+ this.command = command;
2904
+ this.directory = directory;
2905
+ this.flags = flags.map((f) => f.replace(/^--?/, "")).filter((f) => f.length > 0).slice(0, 40);
2906
+ this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
2907
+ }
2908
+ recordStep(name, durationMs, ok = true, counts) {
2909
+ if (this.steps.length >= 200) return;
2910
+ this.steps.push({
2911
+ seq: this.seq++,
2912
+ name: name.slice(0, 80),
2913
+ ok,
2914
+ ms: Math.max(0, Math.round(durationMs)),
2915
+ counts
2916
+ });
2917
+ }
2918
+ setReport(report) {
2919
+ this.report = report;
2920
+ }
2921
+ setPolicyMeta(version, hash) {
2922
+ this.policyMeta = {
2923
+ version,
2924
+ hash: hash.slice(0, 64)
2925
+ };
2926
+ }
2927
+ setAgentTelemetry(telemetry) {
2928
+ this.agentTelemetry = telemetry;
2929
+ }
2930
+ setGitContext(git) {
2931
+ this.gitContextOverride = {
2932
+ ...this.gitContextOverride,
2933
+ ...git
2934
+ };
2935
+ }
2936
+ buildRunPayload(exitCode) {
2937
+ const repo = detectRepoRef(this.directory) || {
2938
+ provider: "github",
2939
+ slug: "unknown/unknown"
2940
+ };
2941
+ const git = {
2942
+ ...detectGitContext(this.directory),
2943
+ ...this.gitContextOverride
2944
+ };
2945
+ const ci = detectCiContext();
2946
+ const ownerVerified = Boolean(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);
2947
+ return {
2948
+ run_id: this.runId,
2949
+ command: this.command,
2950
+ repo,
2951
+ owner_verified: ownerVerified,
2952
+ git,
2953
+ ci,
2954
+ flags: this.flags,
2955
+ cli_version: APP_VERSION,
2956
+ policy: this.policyMeta,
2957
+ started_at: this.startedAt,
2958
+ ended_at: (/* @__PURE__ */ new Date()).toISOString(),
2959
+ exit_code: exitCode,
2960
+ steps: this.steps.slice(0, 200),
2961
+ report: this.report ? {
2962
+ ...this.report,
2963
+ agent_telemetry: this.agentTelemetry || this.report.agent_telemetry
2964
+ } : void 0,
2965
+ agent_telemetry: this.agentTelemetry
2966
+ };
2967
+ }
2968
+ async finish(exitCode) {
2969
+ if (this.finished) return;
2970
+ this.finished = true;
2971
+ try {
2972
+ enqueueRun(this.buildRunPayload(exitCode));
2973
+ const activeToken = getActiveToken();
2974
+ if (activeToken) await flushOutbox(activeToken.token);
2975
+ } catch {}
2976
+ }
2977
+ };
2978
+ let activeRecorder = null;
2979
+ const getActiveRecorder = () => activeRecorder;
2980
+ const startRunRecorder = (command, directory, flags = []) => {
2981
+ const recorder = new CloudRunRecorder(command, directory, flags);
2982
+ activeRecorder = recorder;
2983
+ const activeToken = getActiveToken();
2984
+ if (activeToken) flushOutbox(activeToken.token).catch(() => {});
2985
+ return recorder;
2986
+ };
2987
+ const finishActiveRecorder = async (exitCode = 0) => {
2988
+ if (activeRecorder) {
2989
+ const rec = activeRecorder;
2990
+ activeRecorder = null;
2991
+ await rec.finish(exitCode);
2992
+ }
2993
+ };
2994
+ //#endregion
2578
2995
  //#region src/commands/agent-local-cli.ts
2579
2996
  const cliPath$1 = () => {
2580
2997
  const arg = process.argv[1];
@@ -3466,6 +3883,47 @@ const runAgentSession = async (selected, resolvedDir, options, started) => {
3466
3883
  published: Boolean(published)
3467
3884
  });
3468
3885
  await tui.finish({ footer: `Done · ${selected.provider.id} · ${Math.round(performance.now() - started)}ms` });
3886
+ const recorder = getActiveRecorder();
3887
+ if (recorder) {
3888
+ let patchDiff;
3889
+ if (created?.worktree?.path) try {
3890
+ patchDiff = (spawnSync("git", [
3891
+ "diff",
3892
+ "-U2",
3893
+ "--no-color",
3894
+ "HEAD"
3895
+ ], {
3896
+ cwd: created.worktree.path,
3897
+ encoding: "utf-8"
3898
+ }).stdout || "").slice(0, 2e4) || void 0;
3899
+ } catch {}
3900
+ if (published) {
3901
+ const prMatch = published.prUrl?.match(/\/pull\/(\d+)/);
3902
+ const prNumber = prMatch ? Number.parseInt(prMatch[1], 10) : void 0;
3903
+ recorder.setGitContext({
3904
+ branch: published.branch,
3905
+ pr_number: prNumber
3906
+ });
3907
+ } else if (options.branch) recorder.setGitContext({ branch: options.branch });
3908
+ const agentTelemetry = {
3909
+ provider: selected.provider.id,
3910
+ before_score: before.score,
3911
+ after_score: verified.after.score,
3912
+ pr_url: published?.prUrl || void 0,
3913
+ patch_diff: patchDiff,
3914
+ duration_ms: Math.round(performance.now() - started)
3915
+ };
3916
+ recorder.setAgentTelemetry(agentTelemetry);
3917
+ recorder.recordStep("agent:baseline", 50, true);
3918
+ recorder.recordStep("agent:verify", Math.round(performance.now() - started), verified.after.score !== null && verified.after.score >= options.targetScore);
3919
+ recorder.setReport({
3920
+ score: verified.after.score,
3921
+ scoreable: true,
3922
+ files_scanned: changedFiles.length,
3923
+ findings: [],
3924
+ agent_telemetry: agentTelemetry
3925
+ });
3926
+ }
3469
3927
  printAgentSessionSummary({
3470
3928
  before,
3471
3929
  after: verified.after,
@@ -7175,178 +7633,70 @@ const pollDeviceAuthorization = async (grant) => {
7175
7633
  try {
7176
7634
  const pollResult = await pollDeviceToken(grant.device_code);
7177
7635
  if (pollResult.status === "pending") continue;
7178
- if (pollResult.status === "slow_down") {
7179
- pollIntervalMs += 5e3;
7180
- continue;
7181
- }
7182
- if (pollResult.status === "denied") {
7183
- log.error(`Login authorization denied: ${pollResult.error ?? "User rejected request."}`);
7184
- process.exitCode = 1;
7185
- return;
7186
- }
7187
- if (pollResult.status === "expired") {
7188
- log.error("Login authorization code expired. Please try running `raigal login` again.");
7189
- process.exitCode = 1;
7190
- return;
7191
- }
7192
- if (pollResult.status === "ok" && pollResult.data) {
7193
- const { access_token, org, user, expires_at } = pollResult.data;
7194
- saveStoredCredentials({
7195
- token: access_token,
7196
- org,
7197
- user,
7198
- expires_at
7199
- });
7200
- process.stdout.write("\n");
7201
- log.success(`Signed in as ${user.email} · ${org.name}`);
7202
- return;
7203
- }
7204
- } catch (err) {
7205
- log.error(`Polling error: ${err instanceof Error ? err.message : String(err)}`);
7206
- process.exitCode = 1;
7207
- return;
7208
- }
7209
- }
7210
- log.error("Login timed out. Please run `raigal login` again.");
7211
- process.exitCode = 1;
7212
- };
7213
- const loginCommand = async (options = {}) => {
7214
- if (options.withToken) {
7215
- await loginWithToken();
7216
- return;
7217
- }
7218
- log.info("Starting browser login...");
7219
- let grant;
7220
- try {
7221
- grant = await startDeviceLogin();
7222
- } catch (err) {
7223
- log.error(`Failed to start login: ${err instanceof Error ? err.message : String(err)}`);
7224
- process.exitCode = 1;
7225
- return;
7226
- }
7227
- process.stdout.write("\n");
7228
- process.stdout.write(` Confirmation code: ${grant.user_code}\n`);
7229
- process.stdout.write(` Verification URL: ${grant.verification_uri_complete}\n\n`);
7230
- log.muted("Opening browser to confirm authorization...");
7231
- openBrowser(grant.verification_uri_complete);
7232
- await pollDeviceAuthorization(grant);
7233
- };
7234
- //#endregion
7235
- //#region src/commands/logout.ts
7236
- const logoutCommand = async () => {
7237
- const active = getActiveToken();
7238
- if (active && active.kind === "session") await revokeSessionApi(active.token);
7239
- clearStoredCredentials();
7240
- clearCachedEntitlement();
7241
- log.success("Signed out and cleared cached credentials.");
7242
- };
7243
- //#endregion
7244
- //#region src/cloud/outbox.ts
7245
- const MAX_OUTBOX_BYTES = 5 * 1024 * 1024;
7246
- const getMaxOutboxBytes = () => {
7247
- const override = process.env.RAIGAL_MAX_OUTBOX_BYTES;
7248
- if (override) {
7249
- const parsed = Number.parseInt(override, 10);
7250
- if (!Number.isNaN(parsed) && parsed > 0) return parsed;
7251
- }
7252
- return MAX_OUTBOX_BYTES;
7253
- };
7254
- const loadOutboxRuns = () => {
7255
- const outboxPath = getOutboxPath();
7256
- if (!fs.existsSync(outboxPath)) return [];
7257
- try {
7258
- const lines = fs.readFileSync(outboxPath, "utf-8").split("\n").filter((l) => l.trim().length > 0);
7259
- const runs = [];
7260
- for (const line of lines) try {
7261
- const parsed = JSON.parse(line);
7262
- const valid = Run.safeParse(parsed);
7263
- if (valid.success) runs.push(valid.data);
7264
- } catch {}
7265
- return runs;
7266
- } catch {
7267
- return [];
7268
- }
7269
- };
7270
- const writeRunsToOutbox = (runs) => {
7271
- const outboxPath = getOutboxPath();
7272
- const dir = path.dirname(outboxPath);
7273
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, {
7274
- recursive: true,
7275
- mode: 448
7276
- });
7277
- let lines = runs.map((r) => JSON.stringify(r));
7278
- let text = lines.length > 0 ? `${lines.join("\n")}\n` : "";
7279
- const maxBytes = getMaxOutboxBytes();
7280
- while (Buffer.byteLength(text, "utf-8") > maxBytes && lines.length > 1) {
7281
- lines.shift();
7282
- text = `${lines.join("\n")}\n`;
7283
- }
7284
- const temp = `${outboxPath}.${Date.now()}.tmp`;
7285
- fs.writeFileSync(temp, text, { mode: 384 });
7286
- try {
7287
- fs.renameSync(temp, outboxPath);
7288
- } catch {
7289
- fs.rmSync(temp, { force: true });
7290
- fs.writeFileSync(outboxPath, text, { mode: 384 });
7291
- }
7292
- };
7293
- const enqueueRun = (run) => {
7294
- const existing = loadOutboxRuns();
7295
- existing.push(run);
7296
- writeRunsToOutbox(existing);
7297
- };
7298
- const getLastRecordedRun = () => {
7299
- const runs = loadOutboxRuns();
7300
- return runs.length > 0 ? runs[runs.length - 1] ?? null : null;
7301
- };
7302
- const flushOutbox = async (token) => {
7303
- const runs = loadOutboxRuns();
7304
- if (runs.length === 0) return {
7305
- sent: 0,
7306
- failed: 0,
7307
- remaining: 0
7308
- };
7309
- let sentCount = 0;
7310
- let failCount = 0;
7311
- const remainingRuns = [];
7312
- for (let i = 0; i < runs.length; i++) {
7313
- const run = runs[i];
7314
- try {
7315
- await requestApi("/v1/runs", {
7316
- method: "POST",
7317
- token,
7318
- body: run,
7319
- timeoutMs: 4e3
7320
- });
7321
- sentCount++;
7322
- } catch (err) {
7323
- if (err instanceof ApiError) {
7324
- if ([
7325
- 400,
7326
- 401,
7327
- 403,
7328
- 413,
7329
- 422
7330
- ].includes(err.status)) {
7331
- failCount++;
7332
- continue;
7333
- }
7334
- if (err.status === 409) {
7335
- sentCount++;
7336
- continue;
7337
- }
7636
+ if (pollResult.status === "slow_down") {
7637
+ pollIntervalMs += 5e3;
7638
+ continue;
7338
7639
  }
7339
- failCount++;
7340
- remainingRuns.push(...runs.slice(i));
7341
- break;
7640
+ if (pollResult.status === "denied") {
7641
+ log.error(`Login authorization denied: ${pollResult.error ?? "User rejected request."}`);
7642
+ process.exitCode = 1;
7643
+ return;
7644
+ }
7645
+ if (pollResult.status === "expired") {
7646
+ log.error("Login authorization code expired. Please try running `raigal login` again.");
7647
+ process.exitCode = 1;
7648
+ return;
7649
+ }
7650
+ if (pollResult.status === "ok" && pollResult.data) {
7651
+ const { access_token, org, user, expires_at } = pollResult.data;
7652
+ saveStoredCredentials({
7653
+ token: access_token,
7654
+ org,
7655
+ user,
7656
+ expires_at
7657
+ });
7658
+ process.stdout.write("\n");
7659
+ log.success(`Signed in as ${user.email} · ${org.name}`);
7660
+ return;
7661
+ }
7662
+ } catch (err) {
7663
+ log.error(`Polling error: ${err instanceof Error ? err.message : String(err)}`);
7664
+ process.exitCode = 1;
7665
+ return;
7342
7666
  }
7343
7667
  }
7344
- writeRunsToOutbox(remainingRuns);
7345
- return {
7346
- sent: sentCount,
7347
- failed: failCount,
7348
- remaining: remainingRuns.length
7349
- };
7668
+ log.error("Login timed out. Please run `raigal login` again.");
7669
+ process.exitCode = 1;
7670
+ };
7671
+ const loginCommand = async (options = {}) => {
7672
+ if (options.withToken) {
7673
+ await loginWithToken();
7674
+ return;
7675
+ }
7676
+ log.info("Starting browser login...");
7677
+ let grant;
7678
+ try {
7679
+ grant = await startDeviceLogin();
7680
+ } catch (err) {
7681
+ log.error(`Failed to start login: ${err instanceof Error ? err.message : String(err)}`);
7682
+ process.exitCode = 1;
7683
+ return;
7684
+ }
7685
+ process.stdout.write("\n");
7686
+ process.stdout.write(` Confirmation code: ${grant.user_code}\n`);
7687
+ process.stdout.write(` Verification URL: ${grant.verification_uri_complete}\n\n`);
7688
+ log.muted("Opening browser to confirm authorization...");
7689
+ openBrowser(grant.verification_uri_complete);
7690
+ await pollDeviceAuthorization(grant);
7691
+ };
7692
+ //#endregion
7693
+ //#region src/commands/logout.ts
7694
+ const logoutCommand = async () => {
7695
+ const active = getActiveToken();
7696
+ if (active && active.kind === "session") await revokeSessionApi(active.token);
7697
+ clearStoredCredentials();
7698
+ clearCachedEntitlement();
7699
+ log.success("Signed out and cleared cached credentials.");
7350
7700
  };
7351
7701
  //#endregion
7352
7702
  //#region src/commands/telemetry-show.ts
@@ -7450,6 +7800,14 @@ const registerCloudCommands = (program) => {
7450
7800
  if (flags.show || isJson) await telemetryShowCommand({ json: isJson });
7451
7801
  else process.stdout.write("Run `raigal telemetry --show` to inspect recorded telemetry payloads.\n");
7452
7802
  });
7803
+ program.command("report-pr-closed").description("Report a pull request closed/merged event to Raigal Cloud").option("--pr <number>", "PR number (default: read from GITHUB_EVENT_PATH)").option("--merged", "Flag indicating whether PR was merged").action(async (flags) => {
7804
+ const prNumber = flags.pr ? Number.parseInt(flags.pr, 10) : void 0;
7805
+ const { reportPrClosedCommand } = await import("./report-pr-closed-CDdQjRWj.js");
7806
+ await reportPrClosedCommand({
7807
+ prNumber,
7808
+ merged: Boolean(flags.merged)
7809
+ });
7810
+ });
7453
7811
  };
7454
7812
  //#endregion
7455
7813
  //#region src/hooks/feedback.ts
@@ -23984,7 +24342,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
23984
24342
  if (!process.stderr.isTTY) return;
23985
24343
  const cachePath = resolveUpdateCachePath();
23986
24344
  const cache = readCache(cachePath);
23987
- if (cache && isOutdated("1.0.1", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
24345
+ if (cache && isOutdated("1.0.2", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
23988
24346
  if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
23989
24347
  const latest = await fetchLatestVersion$1();
23990
24348
  if (latest) writeCache(cachePath, {
@@ -24504,7 +24862,7 @@ const renderCommandGroup = (group, commandWidth) => {
24504
24862
  return lines;
24505
24863
  };
24506
24864
  const renderCommandReference = (input = {}) => {
24507
- const version = input.version ?? "1.0.1";
24865
+ const version = input.version ?? "1.0.2";
24508
24866
  const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
24509
24867
  const lines = [renderHeader({
24510
24868
  version,
@@ -24727,7 +25085,7 @@ const renderHelpDetails = () => [
24727
25085
  ].map((line) => highlightRaigal(line, theme)).join("\n");
24728
25086
  const renderHome = (input = {}) => {
24729
25087
  let out = renderHeader({
24730
- version: input.version ?? "1.0.1",
25088
+ version: input.version ?? "1.0.2",
24731
25089
  command: "--bare",
24732
25090
  context: []
24733
25091
  });
@@ -24885,100 +25243,15 @@ const matchFixAgent = (flags) => {
24885
25243
  return FIX_AGENT_FLAGS.find((a) => flags[a.name])?.flag;
24886
25244
  };
24887
25245
  //#endregion
24888
- //#region src/cloud/context.ts
24889
- const execGit = (args, cwd) => {
24890
- try {
24891
- const res = spawnSync("git", args, {
24892
- cwd: path.resolve(cwd),
24893
- encoding: "utf-8",
24894
- stdio: [
24895
- "ignore",
24896
- "pipe",
24897
- "ignore"
24898
- ]
24899
- });
24900
- if (res.error || res.status !== 0) return void 0;
24901
- return res.stdout.trim() || void 0;
24902
- } catch {
24903
- return;
24904
- }
24905
- };
24906
- const getRootCommitHash = (directory) => {
24907
- const hash = execGit([
24908
- "rev-list",
24909
- "--max-parents=0",
24910
- "HEAD"
24911
- ], directory);
24912
- if (!hash) return void 0;
24913
- const firstLine = hash.split("\n")[0]?.trim();
24914
- return firstLine && /^[0-9a-f]{40,64}$/i.test(firstLine) ? firstLine : void 0;
24915
- };
24916
- const detectRepoRef = (directory, ownerId) => {
24917
- const detected = detectLocalRepoOwner(directory);
24918
- if (!detected.owner || !detected.repo) return null;
24919
- return {
24920
- provider: "github",
24921
- slug: `${detected.owner}/${detected.repo}`.toLowerCase(),
24922
- owner_id: ownerId,
24923
- root_commit_hash: getRootCommitHash(directory)
24924
- };
24925
- };
24926
- const readGitHubEventPayload = () => {
24927
- const eventPath = process.env.GITHUB_EVENT_PATH;
24928
- if (!eventPath || !fs.existsSync(eventPath)) return null;
24929
- try {
24930
- const content = fs.readFileSync(eventPath, "utf-8");
24931
- return JSON.parse(content);
24932
- } catch {
24933
- return null;
24934
- }
24935
- };
24936
- const detectGitContext = (directory) => {
24937
- const eventPayload = readGitHubEventPayload();
24938
- const pr = eventPayload?.pull_request;
24939
- let headSha = pr?.head?.sha;
24940
- if (!headSha) headSha = process.env.GITHUB_SHA || execGit(["rev-parse", "HEAD"], directory);
24941
- let branch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;
24942
- if (!branch) {
24943
- branch = execGit([
24944
- "rev-parse",
24945
- "--abbrev-ref",
24946
- "HEAD"
24947
- ], directory);
24948
- if (branch === "HEAD") branch = void 0;
24949
- }
24950
- let baseRef = pr?.base?.ref || process.env.GITHUB_BASE_REF;
24951
- if (!baseRef) baseRef = void 0;
24952
- let prNumber;
24953
- if (typeof pr?.number === "number") prNumber = pr.number;
24954
- else if (typeof eventPayload?.number === "number") prNumber = eventPayload.number;
24955
- else if (process.env.GITHUB_REF) {
24956
- const match = process.env.GITHUB_REF.match(/^refs\/pull\/(\d+)\//);
24957
- if (match && match[1]) prNumber = Number.parseInt(match[1], 10);
24958
- }
24959
- const result = {};
24960
- if (branch) result.branch = branch;
24961
- if (headSha) result.head_sha = headSha;
24962
- if (baseRef) result.base_ref = baseRef;
24963
- if (prNumber !== void 0 && !Number.isNaN(prNumber)) result.pr_number = prNumber;
24964
- return result;
24965
- };
24966
- const detectCiContext = () => {
24967
- if (process.env.GITHUB_ACTIONS === "true" || process.env.GITHUB_ACTIONS === "1") {
24968
- const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
24969
- const repo = process.env.GITHUB_REPOSITORY;
24970
- const runId = process.env.GITHUB_RUN_ID;
24971
- let runUrl;
24972
- if (repo && runId) {
24973
- const attempt = process.env.GITHUB_RUN_ATTEMPT;
24974
- runUrl = `${serverUrl}/${repo}/actions/runs/${runId}${attempt ? `/attempts/${attempt}` : ""}`;
24975
- }
24976
- return {
24977
- provider: "github-actions",
24978
- ...runUrl ? { run_url: runUrl } : {}
24979
- };
24980
- }
24981
- if (process.env.CI === "true" || process.env.CI === "1") return { provider: process.env.GITLAB_CI && "gitlab-ci" || process.env.BITBUCKET_BUILD_NUMBER && "bitbucket-pipelines" || process.env.CIRCLECI && "circleci" || process.env.BUILDKITE && "buildkite" || process.env.TRAVIS && "travis-ci" || "generic-ci" };
25246
+ //#region src/output/rule-message.ts
25247
+ /**
25248
+ * Pure deterministic formatter for diagnostic messages.
25249
+ * Secret-class rules (or redactSource: true) unconditionally return FIXED_SECRET_DIAGNOSTIC_MESSAGE.
25250
+ * Non-secret rules return diagnostic.message (clamped to max 500 chars).
25251
+ */
25252
+ const formatFindingMessage = (diagnostic) => {
25253
+ if (diagnostic.redactSource || isSecretClassRule(diagnostic.rule)) return FIXED_SECRET_DIAGNOSTIC_MESSAGE;
25254
+ return diagnostic.message.slice(0, 500);
24982
25255
  };
24983
25256
  //#endregion
24984
25257
  //#region src/cloud/fingerprint.ts
@@ -25020,7 +25293,8 @@ const toContractFinding = (diagnostic, rootDirectory, lineTextOverride) => {
25020
25293
  severity,
25021
25294
  path: relativePath,
25022
25295
  line: Math.max(0, diagnostic.line),
25023
- fingerprint
25296
+ fingerprint,
25297
+ message: formatFindingMessage(diagnostic)
25024
25298
  };
25025
25299
  };
25026
25300
  //#endregion
@@ -25111,96 +25385,6 @@ const applyPolicyToConfig = (localConfig, policy) => {
25111
25385
  };
25112
25386
  };
25113
25387
  //#endregion
25114
- //#region src/cloud/recorder.ts
25115
- var CloudRunRecorder = class {
25116
- runId;
25117
- command;
25118
- directory;
25119
- flags;
25120
- startedAt;
25121
- steps = [];
25122
- report;
25123
- policyMeta;
25124
- seq = 0;
25125
- finished = false;
25126
- constructor(command, directory, flags = []) {
25127
- this.runId = randomUUID();
25128
- this.command = command;
25129
- this.directory = directory;
25130
- this.flags = flags.map((f) => f.replace(/^--?/, "")).filter((f) => f.length > 0).slice(0, 40);
25131
- this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
25132
- }
25133
- recordStep(name, durationMs, ok = true, counts) {
25134
- if (this.steps.length >= 200) return;
25135
- this.steps.push({
25136
- seq: this.seq++,
25137
- name: name.slice(0, 80),
25138
- ok,
25139
- ms: Math.max(0, Math.round(durationMs)),
25140
- counts
25141
- });
25142
- }
25143
- setReport(report) {
25144
- this.report = report;
25145
- }
25146
- setPolicyMeta(version, hash) {
25147
- this.policyMeta = {
25148
- version,
25149
- hash: hash.slice(0, 64)
25150
- };
25151
- }
25152
- buildRunPayload(exitCode) {
25153
- const repo = detectRepoRef(this.directory) || {
25154
- provider: "github",
25155
- slug: "unknown/unknown"
25156
- };
25157
- const git = detectGitContext(this.directory);
25158
- const ci = detectCiContext();
25159
- const ownerVerified = Boolean(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);
25160
- return {
25161
- run_id: this.runId,
25162
- command: this.command,
25163
- repo,
25164
- owner_verified: ownerVerified,
25165
- git,
25166
- ci,
25167
- flags: this.flags,
25168
- cli_version: APP_VERSION,
25169
- policy: this.policyMeta,
25170
- started_at: this.startedAt,
25171
- ended_at: (/* @__PURE__ */ new Date()).toISOString(),
25172
- exit_code: exitCode,
25173
- steps: this.steps.slice(0, 200),
25174
- report: this.report
25175
- };
25176
- }
25177
- async finish(exitCode) {
25178
- if (this.finished) return;
25179
- this.finished = true;
25180
- try {
25181
- enqueueRun(this.buildRunPayload(exitCode));
25182
- const activeToken = getActiveToken();
25183
- if (activeToken) await flushOutbox(activeToken.token);
25184
- } catch {}
25185
- }
25186
- };
25187
- let activeRecorder = null;
25188
- const getActiveRecorder = () => activeRecorder;
25189
- const startRunRecorder = (command, directory, flags = []) => {
25190
- const recorder = new CloudRunRecorder(command, directory, flags);
25191
- activeRecorder = recorder;
25192
- const activeToken = getActiveToken();
25193
- if (activeToken) flushOutbox(activeToken.token).catch(() => {});
25194
- return recorder;
25195
- };
25196
- const finishActiveRecorder = async (exitCode = 0) => {
25197
- if (activeRecorder) {
25198
- const rec = activeRecorder;
25199
- activeRecorder = null;
25200
- await rec.finish(exitCode);
25201
- }
25202
- };
25203
- //#endregion
25204
25388
  //#region src/engagement/pilot-invitation.ts
25205
25389
  const STATE_BASENAME = "pilot_invitation.scans";
25206
25390
  const INVITATION_SCAN_THRESHOLD = 3;
@@ -26435,12 +26619,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
26435
26619
  scoreFileCount
26436
26620
  });
26437
26621
  if (options.sarif) {
26438
- const { buildSarifLog } = await import("./sarif-B-l1Fhzd.js");
26622
+ const { buildSarifLog } = await import("./sarif--DI_2p2r.js");
26439
26623
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
26440
26624
  return completion;
26441
26625
  }
26442
26626
  if (options.json) {
26443
- const { buildJsonOutput } = await import("./json-DBN8fnd3.js");
26627
+ const { buildJsonOutput } = await import("./json-C9fBWj3V.js");
26444
26628
  const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
26445
26629
  console.log(JSON.stringify(jsonOut, null, 2));
26446
26630
  return completion;
@@ -29551,4 +29735,4 @@ const main = async () => {
29551
29735
  };
29552
29736
  main();
29553
29737
  //#endregion
29554
- export { FIXED_SECRET_DIAGNOSTIC_MESSAGE as a, APP_VERSION as i, withFindingAssessments as n, isSecretClassRule as o, ENGINE_INFO as r, summarizeFindingAssessments as t };
29738
+ export { requestApi as a, log as c, detectRepoRef as i, FIXED_SECRET_DIAGNOSTIC_MESSAGE as l, withFindingAssessments as n, getActiveToken as o, ENGINE_INFO as r, APP_VERSION as s, summarizeFindingAssessments as t, isSecretClassRule as u };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-om7oAIVO.js";
1
+ import { t as APP_VERSION } from "./version-DW_ZBtce.js";
2
2
  import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-M5fUTE-C.js";
3
3
  import { n as isSecretClassRule, r as maskSecrets, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
4
4
  import { createRequire, isBuiltin } from "node:module";
@@ -15803,7 +15803,8 @@ const Finding = z.object({
15803
15803
  severity: Severity,
15804
15804
  path: z.string().max(500),
15805
15805
  line: z.number().int().nonnegative(),
15806
- fingerprint: z.string().length(32)
15806
+ fingerprint: z.string().length(32),
15807
+ message: z.string().max(500).optional()
15807
15808
  });
15808
15809
  const RepoRef = z.object({
15809
15810
  provider: z.literal("github"),
@@ -15818,12 +15819,21 @@ const Step = z.object({
15818
15819
  ms: z.number().int().nonnegative(),
15819
15820
  counts: z.record(z.string(), z.number()).optional()
15820
15821
  });
15822
+ const AgentTelemetry = z.object({
15823
+ provider: z.string().max(80),
15824
+ before_score: z.number().min(0).max(100).nullable(),
15825
+ after_score: z.number().min(0).max(100).nullable(),
15826
+ pr_url: z.string().url().max(500).optional(),
15827
+ patch_diff: z.string().max(2e4).optional(),
15828
+ duration_ms: z.number().int().nonnegative().optional()
15829
+ });
15821
15830
  const Report = z.object({
15822
15831
  score: z.number().min(0).max(100).nullable(),
15823
15832
  scoreable: z.boolean(),
15824
15833
  engine_scores: z.record(z.string(), z.number()).optional(),
15825
15834
  files_scanned: z.number().int().nonnegative(),
15826
- findings: z.array(Finding).max(5e3)
15835
+ findings: z.array(Finding).max(5e3),
15836
+ agent_telemetry: AgentTelemetry.optional()
15827
15837
  });
15828
15838
  z.object({
15829
15839
  run_id: z.uuid(),
@@ -15840,7 +15850,13 @@ z.object({
15840
15850
  branch: z.string().max(200).optional(),
15841
15851
  head_sha: z.string().max(64).optional(),
15842
15852
  base_ref: z.string().max(200).optional(),
15843
- pr_number: z.number().int().optional()
15853
+ pr_number: z.number().int().optional(),
15854
+ diff_summary: z.object({
15855
+ files_changed: z.number().int().nonnegative(),
15856
+ additions: z.number().int().nonnegative(),
15857
+ deletions: z.number().int().nonnegative()
15858
+ }).optional(),
15859
+ diff_preview: z.string().max(1e4).optional()
15844
15860
  }),
15845
15861
  ci: z.object({
15846
15862
  provider: z.string().max(40),
@@ -15856,7 +15872,8 @@ z.object({
15856
15872
  ended_at: z.iso.datetime(),
15857
15873
  exit_code: z.number().int(),
15858
15874
  steps: z.array(Step).max(200),
15859
- report: Report.optional()
15875
+ report: Report.optional(),
15876
+ agent_telemetry: AgentTelemetry.optional()
15860
15877
  });
15861
15878
  const Policy = z.object({
15862
15879
  version: z.number().int(),
@@ -16051,7 +16068,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
16051
16068
  const claims = parsed.data;
16052
16069
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
16053
16070
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
16054
- if (!isVersionAtLeast("1.0.1", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
16071
+ if (!isVersionAtLeast("1.0.2", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
16055
16072
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
16056
16073
  const grace = options.allowGraceSeconds ?? 0;
16057
16074
  if (now > claims.exp + grace) {
@@ -17916,6 +17933,17 @@ const detectRepoRef = (directory, ownerId) => {
17916
17933
  };
17917
17934
  };
17918
17935
  //#endregion
17936
+ //#region src/output/rule-message.ts
17937
+ /**
17938
+ * Pure deterministic formatter for diagnostic messages.
17939
+ * Secret-class rules (or redactSource: true) unconditionally return FIXED_SECRET_DIAGNOSTIC_MESSAGE.
17940
+ * Non-secret rules return diagnostic.message (clamped to max 500 chars).
17941
+ */
17942
+ const formatFindingMessage = (diagnostic) => {
17943
+ if (diagnostic.redactSource || isSecretClassRule(diagnostic.rule)) return FIXED_SECRET_DIAGNOSTIC_MESSAGE;
17944
+ return diagnostic.message.slice(0, 500);
17945
+ };
17946
+ //#endregion
17919
17947
  //#region src/cloud/fingerprint.ts
17920
17948
  /**
17921
17949
  * Normalizes line text: trims leading/trailing whitespace and compresses
@@ -17955,7 +17983,8 @@ const toContractFinding = (diagnostic, rootDirectory, lineTextOverride) => {
17955
17983
  severity,
17956
17984
  path: relativePath,
17957
17985
  line: Math.max(0, diagnostic.line),
17958
- fingerprint
17986
+ fingerprint,
17987
+ message: formatFindingMessage(diagnostic)
17959
17988
  };
17960
17989
  };
17961
17990
  //#endregion
@@ -19171,12 +19200,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
19171
19200
  scoreFileCount
19172
19201
  });
19173
19202
  if (options.sarif) {
19174
- const { buildSarifLog } = await import("./sarif-C9k9QYGi.js");
19203
+ const { buildSarifLog } = await import("./sarif-BHrT2Drf.js");
19175
19204
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
19176
19205
  return completion;
19177
19206
  }
19178
19207
  if (options.json) {
19179
- const { buildJsonOutput } = await import("./json-Be-AUuDb.js");
19208
+ const { buildJsonOutput } = await import("./json-CjTR6e4V.js");
19180
19209
  const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
19181
19210
  console.log(JSON.stringify(jsonOut, null, 2));
19182
19211
  return completion;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as APP_VERSION, n as withFindingAssessments, r as ENGINE_INFO, t as summarizeFindingAssessments } from "./cli.js";
2
+ import { n as withFindingAssessments, r as ENGINE_INFO, s as APP_VERSION, t as summarizeFindingAssessments } from "./cli.js";
3
3
  //#region src/output/json.ts
4
4
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs, coverage) => {
5
5
  const allDiagnostics = results.flatMap((r) => r.diagnostics);
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-om7oAIVO.js";
1
+ import { t as APP_VERSION } from "./version-DW_ZBtce.js";
2
2
  import { i as ENGINE_INFO, n as withFindingAssessments, t as summarizeFindingAssessments } from "./finding-assessment-M5fUTE-C.js";
3
3
  //#region src/output/json.ts
4
4
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs, coverage) => {
package/dist/mcp.js CHANGED
@@ -84,7 +84,7 @@ const getActiveToken = () => {
84
84
  };
85
85
  //#endregion
86
86
  //#region src/version.ts
87
- const APP_VERSION = "1.0.1";
87
+ const APP_VERSION = "1.0.2";
88
88
  //#endregion
89
89
  //#region src/cloud/contract.ts
90
90
  const Severity = z.enum(["error", "warning"]);
@@ -94,7 +94,8 @@ const Finding = z.object({
94
94
  severity: Severity,
95
95
  path: z.string().max(500),
96
96
  line: z.number().int().nonnegative(),
97
- fingerprint: z.string().length(32)
97
+ fingerprint: z.string().length(32),
98
+ message: z.string().max(500).optional()
98
99
  });
99
100
  const RepoRef = z.object({
100
101
  provider: z.literal("github"),
@@ -109,12 +110,21 @@ const Step = z.object({
109
110
  ms: z.number().int().nonnegative(),
110
111
  counts: z.record(z.string(), z.number()).optional()
111
112
  });
113
+ const AgentTelemetry = z.object({
114
+ provider: z.string().max(80),
115
+ before_score: z.number().min(0).max(100).nullable(),
116
+ after_score: z.number().min(0).max(100).nullable(),
117
+ pr_url: z.string().url().max(500).optional(),
118
+ patch_diff: z.string().max(2e4).optional(),
119
+ duration_ms: z.number().int().nonnegative().optional()
120
+ });
112
121
  const Report = z.object({
113
122
  score: z.number().min(0).max(100).nullable(),
114
123
  scoreable: z.boolean(),
115
124
  engine_scores: z.record(z.string(), z.number()).optional(),
116
125
  files_scanned: z.number().int().nonnegative(),
117
- findings: z.array(Finding).max(5e3)
126
+ findings: z.array(Finding).max(5e3),
127
+ agent_telemetry: AgentTelemetry.optional()
118
128
  });
119
129
  z.object({
120
130
  run_id: z.uuid(),
@@ -131,7 +141,13 @@ z.object({
131
141
  branch: z.string().max(200).optional(),
132
142
  head_sha: z.string().max(64).optional(),
133
143
  base_ref: z.string().max(200).optional(),
134
- pr_number: z.number().int().optional()
144
+ pr_number: z.number().int().optional(),
145
+ diff_summary: z.object({
146
+ files_changed: z.number().int().nonnegative(),
147
+ additions: z.number().int().nonnegative(),
148
+ deletions: z.number().int().nonnegative()
149
+ }).optional(),
150
+ diff_preview: z.string().max(1e4).optional()
135
151
  }),
136
152
  ci: z.object({
137
153
  provider: z.string().max(40),
@@ -147,7 +163,8 @@ z.object({
147
163
  ended_at: z.iso.datetime(),
148
164
  exit_code: z.number().int(),
149
165
  steps: z.array(Step).max(200),
150
- report: Report.optional()
166
+ report: Report.optional(),
167
+ agent_telemetry: AgentTelemetry.optional()
151
168
  });
152
169
  z.object({
153
170
  version: z.number().int(),
@@ -342,7 +359,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
342
359
  const claims = parsed.data;
343
360
  const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
344
361
  if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
345
- if (!isVersionAtLeast("1.0.1", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
362
+ if (!isVersionAtLeast("1.0.2", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
346
363
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
347
364
  const grace = options.allowGraceSeconds ?? 0;
348
365
  if (now > claims.exp + grace) {
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { a as requestApi, c as log, i as detectRepoRef, o as getActiveToken } from "./cli.js";
3
+ import fs from "node:fs";
4
+ //#region src/commands/report-pr-closed.ts
5
+ const reportPrClosedCommand = async (options = {}) => {
6
+ try {
7
+ let prNumber = options.prNumber;
8
+ let merged = options.merged ?? false;
9
+ const eventPath = process.env.GITHUB_EVENT_PATH;
10
+ if (eventPath && fs.existsSync(eventPath)) try {
11
+ const eventData = JSON.parse(fs.readFileSync(eventPath, "utf-8"));
12
+ if (typeof eventData.pull_request?.number === "number") prNumber = eventData.pull_request.number;
13
+ else if (typeof eventData.number === "number") prNumber = eventData.number;
14
+ if (typeof eventData.pull_request?.merged === "boolean") merged = eventData.pull_request.merged;
15
+ } catch {}
16
+ if (!prNumber) {
17
+ log.info("No pull request number detected to report closed.");
18
+ return { exitCode: 0 };
19
+ }
20
+ const repo = detectRepoRef(process.cwd());
21
+ if (!repo) {
22
+ log.info("No repository detected.");
23
+ return { exitCode: 0 };
24
+ }
25
+ const token = getActiveToken()?.token;
26
+ await requestApi("/v1/pr-events", {
27
+ method: "POST",
28
+ token,
29
+ body: {
30
+ repo,
31
+ pr_number: prNumber,
32
+ event: "closed",
33
+ merged
34
+ },
35
+ timeoutMs: 3e3
36
+ });
37
+ log.info(`Reported PR #${prNumber} as ${merged ? "merged" : "closed"}.`);
38
+ return { exitCode: 0 };
39
+ } catch {
40
+ return { exitCode: 0 };
41
+ }
42
+ };
43
+ //#endregion
44
+ export { reportPrClosedCommand };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as FIXED_SECRET_DIAGNOSTIC_MESSAGE, i as APP_VERSION, o as isSecretClassRule } from "./cli.js";
2
+ import { l as FIXED_SECRET_DIAGNOSTIC_MESSAGE, s as APP_VERSION, u as isSecretClassRule } from "./cli.js";
3
3
  import path from "node:path";
4
4
  //#region src/output/sarif.ts
5
5
  const SARIF_VERSION = "2.1.0";
@@ -1,4 +1,4 @@
1
- import { t as APP_VERSION } from "./version-om7oAIVO.js";
1
+ import { t as APP_VERSION } from "./version-DW_ZBtce.js";
2
2
  import { n as isSecretClassRule, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
3
3
  import path from "node:path";
4
4
  //#region src/output/sarif.ts
@@ -1,6 +1,6 @@
1
1
  import "node:fs";
2
2
  import "node:url";
3
3
  //#region src/version.ts
4
- const APP_VERSION = "1.0.1";
4
+ const APP_VERSION = "1.0.2";
5
5
  //#endregion
6
6
  export { APP_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@methiu/raigal",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },