@gpc-cli/core 0.9.50 → 0.9.51

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/index.d.ts CHANGED
@@ -1255,6 +1255,77 @@ declare function fetchChangelog(options?: FetchChangelogOptions): Promise<Change
1255
1255
  */
1256
1256
  declare function formatChangelogEntry(entry: ChangelogEntry): string;
1257
1257
 
1258
+ type OutputMode = "md" | "json" | "prompt";
1259
+ interface RawCommit {
1260
+ sha: string;
1261
+ subject: string;
1262
+ body: string;
1263
+ files: string[];
1264
+ additions: number;
1265
+ deletions: number;
1266
+ authorDate: string;
1267
+ }
1268
+ interface ParsedCommit {
1269
+ sha: string;
1270
+ type: string;
1271
+ scope?: string;
1272
+ subject: string;
1273
+ prRef?: string;
1274
+ files: string[];
1275
+ weight: number;
1276
+ isRevert: boolean;
1277
+ isFixup: boolean;
1278
+ authorDate: string;
1279
+ }
1280
+ interface CommitCluster {
1281
+ id: string;
1282
+ label: string;
1283
+ commits: ParsedCommit[];
1284
+ weight: number;
1285
+ primaryType: string;
1286
+ }
1287
+ interface GeneratedChangelog {
1288
+ from: string;
1289
+ to: string;
1290
+ repo: string | null;
1291
+ rawCommitCount: number;
1292
+ commits: ParsedCommit[];
1293
+ clusters: CommitCluster[];
1294
+ grouped: Record<string, ParsedCommit[]>;
1295
+ headlineCandidates: CommitCluster[];
1296
+ warnings: string[];
1297
+ }
1298
+ interface GenerateOptions {
1299
+ from?: string;
1300
+ to?: string;
1301
+ cwd?: string;
1302
+ repo?: string;
1303
+ }
1304
+ interface GitRunner {
1305
+ log(args: {
1306
+ from: string;
1307
+ to: string;
1308
+ cwd: string;
1309
+ }): Promise<RawCommit[]>;
1310
+ describeLatestTag(cwd: string): Promise<string | null>;
1311
+ verifyRef(ref: string, cwd: string): Promise<boolean>;
1312
+ remoteUrl(cwd: string): Promise<string | null>;
1313
+ }
1314
+ declare const SECTION_ORDER: readonly ["breaking", "feat", "fix", "perf", "docs", "ci", "release", "other"];
1315
+ declare const defaultGitRunner: GitRunner;
1316
+ declare function parseRemoteUrl(url: string | null): string | null;
1317
+ declare function parseCommit(raw: RawCommit): ParsedCommit;
1318
+ declare function generateChangelog(opts?: GenerateOptions, runner?: GitRunner): Promise<GeneratedChangelog>;
1319
+
1320
+ declare function renderMarkdown(g: GeneratedChangelog): string;
1321
+
1322
+ declare function renderJson(g: GeneratedChangelog): string;
1323
+
1324
+ declare function renderPrompt(g: GeneratedChangelog): string;
1325
+
1326
+ type Renderer = (g: GeneratedChangelog) => string;
1327
+ declare const RENDERERS: Record<OutputMode, Renderer>;
1328
+
1258
1329
  interface RtdnStatus {
1259
1330
  topicName: string | null;
1260
1331
  enabled: boolean;
@@ -1300,4 +1371,4 @@ declare function decodeNotification(base64Payload: string): DecodedNotification;
1300
1371
  */
1301
1372
  declare function formatNotification(notification: DecodedNotification): Record<string, unknown>;
1302
1373
 
1303
- export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_PREFLIGHT_CONFIG, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, GpcError, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type MigrationResult, NetworkError, type OneTimeProductDiff, PERMISSION_PROPAGATION_WARNING, type ParsedManifest, type ParsedMonth, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RtdnStatus, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type UploadResult, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchOptions, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchGetOrders, batchSyncInAppProducts, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, deactivateBasePlan, deactivateOffer, decodeNotification, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseFastfile, parseGrantArg, parseMonth, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, replyToReview, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
1374
+ export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, type CommitCluster, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_PREFLIGHT_CONFIG, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, type ParsedCommit, type ParsedManifest, type ParsedMonth, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type UploadResult, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchOptions, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchGetOrders, batchSyncInAppProducts, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderJson, renderMarkdown, renderPrompt, replyToReview, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
package/dist/index.js CHANGED
@@ -3723,10 +3723,10 @@ ${bullets}`);
3723
3723
  }
3724
3724
  return { text, truncated };
3725
3725
  }
3726
- async function gitExec(args) {
3726
+ async function gitExec(args, opts) {
3727
3727
  try {
3728
- const { stdout } = await execFile("git", args);
3729
- return stdout.trim();
3728
+ const result = opts?.cwd ? await execFile("git", args, { encoding: "utf8", cwd: opts.cwd }) : await execFile("git", args);
3729
+ return result.stdout.trim();
3730
3730
  } catch (error) {
3731
3731
  const err = error;
3732
3732
  if (err.code === "ENOENT") {
@@ -7361,6 +7361,533 @@ function formatChangelogEntry(entry) {
7361
7361
  return lines.join("\n");
7362
7362
  }
7363
7363
 
7364
+ // src/commands/changelog-generate.ts
7365
+ var KNOWN_TYPES = /* @__PURE__ */ new Set([
7366
+ "feat",
7367
+ "fix",
7368
+ "perf",
7369
+ "breaking",
7370
+ "docs",
7371
+ "ci",
7372
+ "chore",
7373
+ "refactor",
7374
+ "test",
7375
+ "build",
7376
+ "style",
7377
+ "release"
7378
+ ]);
7379
+ var FILTERED_TYPES = /* @__PURE__ */ new Set(["chore", "refactor", "test", "build", "style", "merge"]);
7380
+ var SECTION_ORDER = ["breaking", "feat", "fix", "perf", "docs", "ci", "release", "other"];
7381
+ var FIXUP_PATTERNS = [
7382
+ /^wip\b/i,
7383
+ /^fix\s+typo\b/i,
7384
+ /^fix\s+typos\b/i,
7385
+ /^address\s+review\b/i,
7386
+ /^review\s+fixes\b/i,
7387
+ /^fixup!/i,
7388
+ /^squash!/i
7389
+ ];
7390
+ var REVERT_PATTERN = /^Revert\s+"(.+?)"\s*$/i;
7391
+ var VERB_CANONICALIZATIONS = [
7392
+ [/^Added\b/, "add"],
7393
+ [/^Adds\b/, "add"],
7394
+ [/^Add\b/, "add"],
7395
+ [/^Fixed\b/, "fix"],
7396
+ [/^Fixes\b/, "fix"],
7397
+ [/^Fix\b/, "fix"],
7398
+ [/^Updated\b/, "update"],
7399
+ [/^Updates\b/, "update"],
7400
+ [/^Update\b/, "update"],
7401
+ [/^Removed\b/, "remove"],
7402
+ [/^Removes\b/, "remove"],
7403
+ [/^Remove\b/, "remove"]
7404
+ ];
7405
+ var EMOJI_PREFIX = /^(?:[\p{Emoji_Presentation}\p{Extended_Pictographic}]\s*)+/u;
7406
+ var INTERNAL_JARGON = [
7407
+ "mutex",
7408
+ "token bucket",
7409
+ "barrel export",
7410
+ "barrel exports",
7411
+ "homedir",
7412
+ "at module level",
7413
+ "ESM",
7414
+ "tsup",
7415
+ "vi.stubGlobal",
7416
+ "execFile",
7417
+ "lazy-import"
7418
+ ];
7419
+ var RECORD_START = "";
7420
+ var FIELD_SEP = "";
7421
+ var COMMIT_FORMAT = `${RECORD_START}%H${FIELD_SEP}%s${FIELD_SEP}%aI${FIELD_SEP}%b`;
7422
+ function parseRawCommits(stdout) {
7423
+ if (!stdout.trim()) return [];
7424
+ const blocks = stdout.split(RECORD_START).filter((b) => b.trim());
7425
+ const commits = [];
7426
+ for (const block of blocks) {
7427
+ const headerEnd = block.indexOf("\n");
7428
+ const header = headerEnd === -1 ? block : block.slice(0, headerEnd);
7429
+ const rest = headerEnd === -1 ? "" : block.slice(headerEnd + 1);
7430
+ const parts = header.split(FIELD_SEP);
7431
+ const sha = parts[0]?.trim() ?? "";
7432
+ const subject = parts[1]?.trim() ?? "";
7433
+ const authorDate = parts[2]?.trim() ?? "";
7434
+ const body = (parts[3] ?? "").trim();
7435
+ if (!sha) continue;
7436
+ const files = [];
7437
+ let additions = 0;
7438
+ let deletions = 0;
7439
+ for (const line of rest.split("\n")) {
7440
+ const trimmed = line.trim();
7441
+ if (!trimmed) continue;
7442
+ const match = trimmed.match(/^(\d+|-)\s+(\d+|-)\s+(.+)$/);
7443
+ if (match) {
7444
+ const a = match[1] === "-" ? 0 : Number(match[1]);
7445
+ const d = match[2] === "-" ? 0 : Number(match[2]);
7446
+ additions += a;
7447
+ deletions += d;
7448
+ files.push(match[3] ?? "");
7449
+ }
7450
+ }
7451
+ commits.push({ sha, subject, body, files, additions, deletions, authorDate });
7452
+ }
7453
+ return commits;
7454
+ }
7455
+ var defaultGitRunner = {
7456
+ async log({ from, to, cwd }) {
7457
+ const stdout = await gitExec(
7458
+ [
7459
+ "log",
7460
+ "--no-merges",
7461
+ `--format=${COMMIT_FORMAT}`,
7462
+ "--numstat",
7463
+ "--end-of-options",
7464
+ `${from}..${to}`
7465
+ ],
7466
+ { cwd }
7467
+ );
7468
+ return parseRawCommits(stdout);
7469
+ },
7470
+ async describeLatestTag(cwd) {
7471
+ try {
7472
+ const out = await gitExec(["describe", "--tags", "--match", "v*", "--abbrev=0"], { cwd });
7473
+ return out.trim() || null;
7474
+ } catch {
7475
+ return null;
7476
+ }
7477
+ },
7478
+ async verifyRef(ref, cwd) {
7479
+ try {
7480
+ await gitExec(["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`], { cwd });
7481
+ return true;
7482
+ } catch {
7483
+ return false;
7484
+ }
7485
+ },
7486
+ async remoteUrl(cwd) {
7487
+ try {
7488
+ const out = await gitExec(["remote", "get-url", "origin"], { cwd });
7489
+ return out.trim() || null;
7490
+ } catch {
7491
+ return null;
7492
+ }
7493
+ }
7494
+ };
7495
+ function parseRemoteUrl(url) {
7496
+ if (!url) return null;
7497
+ const https = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?\/?$/i);
7498
+ if (https) return `${https[1]}/${https[2]}`;
7499
+ const ssh = url.match(/^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/i);
7500
+ if (ssh) return `${ssh[1]}/${ssh[2]}`;
7501
+ return null;
7502
+ }
7503
+ function stripEmojiAndCanonicalize(subject) {
7504
+ let s = subject.replace(EMOJI_PREFIX, "");
7505
+ for (const [pattern, replacement] of VERB_CANONICALIZATIONS) {
7506
+ if (pattern.test(s)) {
7507
+ s = s.replace(pattern, replacement);
7508
+ break;
7509
+ }
7510
+ }
7511
+ return s.trim();
7512
+ }
7513
+ function isFixupSubject(subject) {
7514
+ return FIXUP_PATTERNS.some((p) => p.test(subject));
7515
+ }
7516
+ var CONVENTIONAL_RE = /^(?<type>\w+)(?:\((?<scope>[^)]+)\))?(?<bang>!?):\s*(?<rest>.+)$/;
7517
+ var PR_REF_RE = /\s*\(#(\d+)\)\s*$/;
7518
+ function parseCommit(raw) {
7519
+ const subject = raw.subject;
7520
+ const revertMatch = subject.match(REVERT_PATTERN);
7521
+ const isRevert = !!revertMatch;
7522
+ const effectiveSubject = revertMatch?.[1] ?? subject;
7523
+ const match = effectiveSubject.match(CONVENTIONAL_RE);
7524
+ let type = "other";
7525
+ let scope;
7526
+ let rest = effectiveSubject;
7527
+ if (match?.groups) {
7528
+ const rawType = match.groups["type"]?.toLowerCase() ?? "other";
7529
+ type = KNOWN_TYPES.has(rawType) ? rawType : "other";
7530
+ scope = match.groups["scope"];
7531
+ rest = match.groups["rest"] ?? effectiveSubject;
7532
+ if (match.groups["bang"]) type = "breaking";
7533
+ }
7534
+ let prRef;
7535
+ const prMatch = rest.match(PR_REF_RE);
7536
+ if (prMatch) {
7537
+ prRef = `#${prMatch[1]}`;
7538
+ rest = rest.replace(PR_REF_RE, "").trim();
7539
+ }
7540
+ const cleaned = stripEmojiAndCanonicalize(rest);
7541
+ const finalSubject = prRef ? `${cleaned} (${prRef})` : cleaned;
7542
+ return {
7543
+ sha: raw.sha,
7544
+ type,
7545
+ scope,
7546
+ subject: finalSubject,
7547
+ prRef,
7548
+ files: raw.files,
7549
+ weight: raw.additions + raw.deletions,
7550
+ isRevert,
7551
+ isFixup: isFixupSubject(rest) || isFixupSubject(subject),
7552
+ authorDate: raw.authorDate
7553
+ };
7554
+ }
7555
+ function dedupRevertPairs(commits) {
7556
+ const reverted = /* @__PURE__ */ new Set();
7557
+ for (const c of commits) {
7558
+ if (c.isRevert) reverted.add(c.subject);
7559
+ }
7560
+ return commits.filter((c) => {
7561
+ if (c.isRevert) return false;
7562
+ if (reverted.has(c.subject)) return false;
7563
+ return true;
7564
+ });
7565
+ }
7566
+ function topPathPrefix(file, depth = 2) {
7567
+ return file.split("/").slice(0, depth).join("/");
7568
+ }
7569
+ function tokenize2(subject) {
7570
+ const STOP = /* @__PURE__ */ new Set([
7571
+ "the",
7572
+ "a",
7573
+ "an",
7574
+ "and",
7575
+ "or",
7576
+ "of",
7577
+ "to",
7578
+ "in",
7579
+ "on",
7580
+ "for",
7581
+ "with",
7582
+ "is",
7583
+ "be",
7584
+ "by",
7585
+ "at"
7586
+ ]);
7587
+ return new Set(
7588
+ subject.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP.has(w))
7589
+ );
7590
+ }
7591
+ function jaccard(a, b) {
7592
+ if (a.size === 0 && b.size === 0) return 0;
7593
+ let inter = 0;
7594
+ for (const t of a) if (b.has(t)) inter++;
7595
+ const union = a.size + b.size - inter;
7596
+ return union === 0 ? 0 : inter / union;
7597
+ }
7598
+ function shouldCluster(a, b, ta, tb) {
7599
+ const aPrefixes = new Set(a.files.map((f) => topPathPrefix(f)));
7600
+ for (const f of b.files) {
7601
+ if (aPrefixes.has(topPathPrefix(f))) return true;
7602
+ }
7603
+ if (jaccard(ta, tb) > 0.4) return true;
7604
+ const aDate = Date.parse(a.authorDate);
7605
+ const bDate = Date.parse(b.authorDate);
7606
+ if (!Number.isNaN(aDate) && !Number.isNaN(bDate)) {
7607
+ const dayDiff = Math.abs(aDate - bDate) / 864e5;
7608
+ if (dayDiff <= 2) {
7609
+ const aFiles = new Set(a.files);
7610
+ for (const f of b.files) if (aFiles.has(f)) return true;
7611
+ }
7612
+ }
7613
+ return false;
7614
+ }
7615
+ var TYPE_PRIORITY = {
7616
+ breaking: 0,
7617
+ feat: 1,
7618
+ fix: 2,
7619
+ perf: 3,
7620
+ docs: 4,
7621
+ ci: 5,
7622
+ release: 6,
7623
+ other: 7
7624
+ };
7625
+ function clusterCommits(commits) {
7626
+ const n = commits.length;
7627
+ const parent = Array.from({ length: n }, (_, i) => i);
7628
+ const find = (i) => {
7629
+ while (parent[i] !== i) {
7630
+ parent[i] = parent[parent[i]];
7631
+ i = parent[i];
7632
+ }
7633
+ return i;
7634
+ };
7635
+ const union = (i, j) => {
7636
+ const ri = find(i);
7637
+ const rj = find(j);
7638
+ if (ri !== rj) parent[ri] = rj;
7639
+ };
7640
+ const tokens = commits.map((c) => tokenize2(c.subject));
7641
+ for (let i = 0; i < n; i++) {
7642
+ for (let j = i + 1; j < n; j++) {
7643
+ if (shouldCluster(commits[i], commits[j], tokens[i], tokens[j])) union(i, j);
7644
+ }
7645
+ }
7646
+ const groups = /* @__PURE__ */ new Map();
7647
+ for (let i = 0; i < n; i++) {
7648
+ const root = find(i);
7649
+ if (!groups.has(root)) groups.set(root, []);
7650
+ groups.get(root).push(commits[i]);
7651
+ }
7652
+ const clusters = [];
7653
+ for (const [, members] of groups) {
7654
+ const weight = members.reduce((s, m) => s + m.weight, 0);
7655
+ const primaryType = members.map((m) => m.type).sort((a, b) => (TYPE_PRIORITY[a] ?? 99) - (TYPE_PRIORITY[b] ?? 99))[0];
7656
+ const label = clusterLabel(members);
7657
+ clusters.push({
7658
+ id: label.toLowerCase().replace(/\s+/g, "-"),
7659
+ label,
7660
+ commits: members,
7661
+ weight,
7662
+ primaryType
7663
+ });
7664
+ }
7665
+ return clusters;
7666
+ }
7667
+ function clusterLabel(members) {
7668
+ const pathCounts = /* @__PURE__ */ new Map();
7669
+ for (const m of members) {
7670
+ for (const f of m.files) {
7671
+ const top = topPathPrefix(f, 2);
7672
+ pathCounts.set(top, (pathCounts.get(top) ?? 0) + 1);
7673
+ }
7674
+ }
7675
+ let bestPath = null;
7676
+ let bestPathCount = 0;
7677
+ for (const [p, c] of pathCounts) {
7678
+ if (c > bestPathCount) {
7679
+ bestPath = p;
7680
+ bestPathCount = c;
7681
+ }
7682
+ }
7683
+ if (bestPath) return bestPath;
7684
+ const tokenCounts = /* @__PURE__ */ new Map();
7685
+ for (const m of members) {
7686
+ for (const t of tokenize2(m.subject)) {
7687
+ tokenCounts.set(t, (tokenCounts.get(t) ?? 0) + 1);
7688
+ }
7689
+ }
7690
+ let bestToken = null;
7691
+ let bestTokenCount = 0;
7692
+ for (const [t, c] of tokenCounts) {
7693
+ if (c > bestTokenCount) {
7694
+ bestToken = t;
7695
+ bestTokenCount = c;
7696
+ }
7697
+ }
7698
+ return bestToken ?? members[0].subject.slice(0, 30);
7699
+ }
7700
+ function scoreHeadlines(clusters) {
7701
+ return [...clusters].sort((a, b) => {
7702
+ if (b.weight !== a.weight) return b.weight - a.weight;
7703
+ return (TYPE_PRIORITY[a.primaryType] ?? 99) - (TYPE_PRIORITY[b.primaryType] ?? 99);
7704
+ }).slice(0, 3);
7705
+ }
7706
+ function lintJargon(commits) {
7707
+ const warnings = [];
7708
+ for (const c of commits) {
7709
+ const lower = c.subject.toLowerCase();
7710
+ for (const word of INTERNAL_JARGON) {
7711
+ if (lower.includes(word.toLowerCase())) {
7712
+ warnings.push(`jargon: "${word}" in subject "${c.subject}" (${c.sha.slice(0, 7)})`);
7713
+ }
7714
+ }
7715
+ }
7716
+ return warnings;
7717
+ }
7718
+ async function generateChangelog(opts = {}, runner = defaultGitRunner) {
7719
+ const cwd = opts.cwd ?? process.cwd();
7720
+ let from = opts.from;
7721
+ if (!from) {
7722
+ from = await runner.describeLatestTag(cwd) ?? void 0;
7723
+ if (!from) {
7724
+ throw new GpcError(
7725
+ "No git tags found and --from was not provided",
7726
+ "CHANGELOG_NO_TAG",
7727
+ 1,
7728
+ "Pass --from <ref> explicitly, or create an initial tag (e.g., git tag v0.0.1)."
7729
+ );
7730
+ }
7731
+ }
7732
+ const to = opts.to ?? "HEAD";
7733
+ if (!await runner.verifyRef(from, cwd)) {
7734
+ throw new GpcError(
7735
+ `Invalid --from ref: "${from}"`,
7736
+ "CHANGELOG_BAD_REF",
7737
+ 1,
7738
+ "Verify the ref exists with: git rev-parse --verify <ref>"
7739
+ );
7740
+ }
7741
+ if (!await runner.verifyRef(to, cwd)) {
7742
+ throw new GpcError(
7743
+ `Invalid --to ref: "${to}"`,
7744
+ "CHANGELOG_BAD_REF",
7745
+ 1,
7746
+ "Verify the ref exists with: git rev-parse --verify <ref>"
7747
+ );
7748
+ }
7749
+ const repo = opts.repo ?? parseRemoteUrl(await runner.remoteUrl(cwd));
7750
+ const raw = await runner.log({ from, to, cwd });
7751
+ const rawCommitCount = raw.length;
7752
+ const parsed = raw.map(parseCommit);
7753
+ const warnings = [];
7754
+ const scopeLeak = parsed.find((c) => c.scope);
7755
+ if (scopeLeak) {
7756
+ warnings.push(
7757
+ `scope: dropped per project convention (e.g., "${scopeLeak.scope}" in ${scopeLeak.sha.slice(0, 7)})`
7758
+ );
7759
+ }
7760
+ const afterRevert = dedupRevertPairs(parsed);
7761
+ const nonFixup = afterRevert.filter((c) => !c.isFixup);
7762
+ const visible = nonFixup.filter((c) => !FILTERED_TYPES.has(c.type));
7763
+ const clusters = clusterCommits(visible);
7764
+ const headlineCandidates = scoreHeadlines(clusters);
7765
+ const grouped = {};
7766
+ for (const c of visible) {
7767
+ if (!grouped[c.type]) grouped[c.type] = [];
7768
+ grouped[c.type].push(c);
7769
+ }
7770
+ warnings.push(...lintJargon(visible));
7771
+ return {
7772
+ from,
7773
+ to,
7774
+ repo,
7775
+ rawCommitCount,
7776
+ commits: parsed,
7777
+ clusters,
7778
+ grouped,
7779
+ headlineCandidates,
7780
+ warnings
7781
+ };
7782
+ }
7783
+
7784
+ // src/commands/changelog-renderers/markdown.ts
7785
+ function safeSubject(s) {
7786
+ return s.replace(/[\r\n]+/g, " ").trim();
7787
+ }
7788
+ function renderMarkdown(g) {
7789
+ if (g.commits.length === 0) {
7790
+ const compare2 = g.repo ? `
7791
+ **Full Changelog**: https://github.com/${g.repo}/compare/${g.from}...${g.to}` : "";
7792
+ return `## What's Changed
7793
+
7794
+ _No notable changes._
7795
+ ${compare2}`.trim();
7796
+ }
7797
+ const lines = ["## What's Changed", ""];
7798
+ let emittedAny = false;
7799
+ for (const type of SECTION_ORDER) {
7800
+ const commits = g.grouped[type] ?? [];
7801
+ if (commits.length === 0) continue;
7802
+ for (const commit of commits) {
7803
+ lines.push(`- ${type}: ${safeSubject(commit.subject)}`);
7804
+ emittedAny = true;
7805
+ }
7806
+ }
7807
+ if (!emittedAny) {
7808
+ lines.push("_No notable changes._");
7809
+ }
7810
+ if (g.repo) {
7811
+ lines.push("");
7812
+ lines.push(`**Full Changelog**: https://github.com/${g.repo}/compare/${g.from}...${g.to}`);
7813
+ }
7814
+ return lines.join("\n");
7815
+ }
7816
+
7817
+ // src/commands/changelog-renderers/json.ts
7818
+ function renderJson(g) {
7819
+ return JSON.stringify(g, null, 2);
7820
+ }
7821
+
7822
+ // src/commands/changelog-renderers/prompt.ts
7823
+ function safeLine(s) {
7824
+ return s.replace(/[\r\n]+/g, " ").trim();
7825
+ }
7826
+ function renderPrompt(g) {
7827
+ const lines = [];
7828
+ lines.push("You are writing a draft of GitHub Release notes from clustered git commits.");
7829
+ lines.push("");
7830
+ lines.push("VOICE RULES (from project conventions):");
7831
+ lines.push("- Terse, present tense, user-facing language");
7832
+ lines.push('- No internal jargon (no "mutex", "token bucket", "barrel exports", "homedir")');
7833
+ lines.push("- One bullet per feature/fix, not one per commit");
7834
+ lines.push("- Drop conventional-commit scopes (e.g., feat(cli) \u2192 feat:)");
7835
+ lines.push("- Open with a single-sentence highlight describing the release theme");
7836
+ lines.push("");
7837
+ lines.push(`RANGE: ${g.from}..${g.to}`);
7838
+ if (g.repo) lines.push(`REPO: ${g.repo}`);
7839
+ lines.push(`COMMITS: ${g.rawCommitCount} raw, ${g.clusters.length} clusters after dedup`);
7840
+ lines.push("");
7841
+ if (g.headlineCandidates.length > 0) {
7842
+ lines.push("HEADLINE CANDIDATES (largest first):");
7843
+ for (const c of g.headlineCandidates) {
7844
+ lines.push(` ${c.label} (weight ${c.weight}, ${c.commits.length} commits, primary ${c.primaryType})`);
7845
+ }
7846
+ lines.push("");
7847
+ }
7848
+ lines.push("CLUSTERED COMMITS:");
7849
+ lines.push("");
7850
+ for (const cluster of g.clusters) {
7851
+ lines.push(`[cluster: ${cluster.label}, weight ${cluster.weight}, ${cluster.commits.length} commits, primary ${cluster.primaryType}]`);
7852
+ for (const commit of cluster.commits) {
7853
+ lines.push(`- ${commit.type}: ${safeLine(commit.subject)} (${commit.sha.slice(0, 7)})`);
7854
+ if (commit.files.length > 0) {
7855
+ const fileSummary = commit.files.slice(0, 3).join(", ");
7856
+ const more = commit.files.length > 3 ? ` (+${commit.files.length - 3} more)` : "";
7857
+ lines.push(` files: ${fileSummary}${more}`);
7858
+ }
7859
+ }
7860
+ lines.push("");
7861
+ }
7862
+ if (g.warnings.length > 0) {
7863
+ lines.push("LINTER WARNINGS (review before publishing):");
7864
+ for (const w of g.warnings) lines.push(` - ${w}`);
7865
+ lines.push("");
7866
+ }
7867
+ const compare2 = g.repo ? `https://github.com/${g.repo}/compare/${g.from}...${g.to}` : `${g.from}..${g.to}`;
7868
+ lines.push("OUTPUT FORMAT (match exactly):");
7869
+ lines.push("```markdown");
7870
+ lines.push("<one-sentence highlight>");
7871
+ lines.push("");
7872
+ lines.push("## What's Changed");
7873
+ lines.push("");
7874
+ lines.push("- breaking: ...");
7875
+ lines.push("- feat: ...");
7876
+ lines.push("- fix: ...");
7877
+ lines.push("- perf: ...");
7878
+ lines.push("");
7879
+ lines.push(`**Full Changelog**: ${compare2}`);
7880
+ lines.push("```");
7881
+ return lines.join("\n");
7882
+ }
7883
+
7884
+ // src/commands/changelog-renderers/index.ts
7885
+ var RENDERERS = {
7886
+ md: renderMarkdown,
7887
+ json: renderJson,
7888
+ prompt: renderPrompt
7889
+ };
7890
+
7364
7891
  // src/commands/rtdn.ts
7365
7892
  var SUBSCRIPTION_NOTIFICATION_TYPES = {
7366
7893
  1: "SUBSCRIPTION_RECOVERED",
@@ -7463,6 +7990,8 @@ export {
7463
7990
  NetworkError,
7464
7991
  PERMISSION_PROPAGATION_WARNING,
7465
7992
  PluginManager,
7993
+ RENDERERS,
7994
+ SECTION_ORDER,
7466
7995
  SENSITIVE_ARG_KEYS,
7467
7996
  SENSITIVE_KEYS,
7468
7997
  SEVERITY_ORDER,
@@ -7506,6 +8035,7 @@ export {
7506
8035
  deactivateBasePlan,
7507
8036
  deactivateOffer,
7508
8037
  decodeNotification,
8038
+ defaultGitRunner,
7509
8039
  deferSubscriptionPurchase,
7510
8040
  deferSubscriptionV2,
7511
8041
  deleteBasePlan,
@@ -7544,6 +8074,7 @@ export {
7544
8074
  formatStatusSummary,
7545
8075
  formatStatusTable,
7546
8076
  formatWordDiff,
8077
+ generateChangelog,
7547
8078
  generateMigrationPlan,
7548
8079
  generateNotesFromGit,
7549
8080
  getAllScannerNames,
@@ -7617,9 +8148,11 @@ export {
7617
8148
  maybePaginate,
7618
8149
  migratePrices,
7619
8150
  parseAppfile,
8151
+ parseCommit,
7620
8152
  parseFastfile,
7621
8153
  parseGrantArg,
7622
8154
  parseMonth,
8155
+ parseRemoteUrl,
7623
8156
  pauseTrain,
7624
8157
  promoteRelease,
7625
8158
  publish,
@@ -7635,6 +8168,9 @@ export {
7635
8168
  relativeTime,
7636
8169
  removeTesters,
7637
8170
  removeUser,
8171
+ renderJson,
8172
+ renderMarkdown,
8173
+ renderPrompt,
7638
8174
  replyToReview,
7639
8175
  revokeSubscriptionPurchase,
7640
8176
  runPreflight,