claude-git-hooks 2.51.2 → 2.61.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/CHANGELOG.md +275 -30
- package/lib/commands/back-merge.js +91 -11
- package/lib/commands/bump-version.js +39 -0
- package/lib/commands/create-pr.js +73 -33
- package/lib/commands/create-release.js +198 -52
- package/lib/messages/library-warnings.js +29 -0
- package/lib/utils/github-api.js +30 -0
- package/lib/utils/linter-runner.js +6 -0
- package/package.json +83 -83
|
@@ -39,7 +39,8 @@ import {
|
|
|
39
39
|
checkoutBranch,
|
|
40
40
|
pushBranch,
|
|
41
41
|
deleteRemoteBranch,
|
|
42
|
-
createCommit
|
|
42
|
+
createCommit,
|
|
43
|
+
stageFiles
|
|
43
44
|
} from '../utils/git-operations.js';
|
|
44
45
|
import {
|
|
45
46
|
getLatestLocalTag,
|
|
@@ -650,6 +651,53 @@ export async function runBackMerge(args) {
|
|
|
650
651
|
showSuccess(`✓ Merge committed: ${commitMsg}`);
|
|
651
652
|
console.log('');
|
|
652
653
|
|
|
654
|
+
// 14b. Co-change correlation pipeline (AUT-3777)
|
|
655
|
+
let coChangeReport = null;
|
|
656
|
+
try {
|
|
657
|
+
const { coChangePipeline } = await import('../../.library/librarian/index.js');
|
|
658
|
+
|
|
659
|
+
showInfo('Running co-change correlation pipeline...');
|
|
660
|
+
const summary = coChangePipeline({ repoCtx: {} });
|
|
661
|
+
const { modifiedFiles, perStep, report, warnings, mode } = summary;
|
|
662
|
+
|
|
663
|
+
// Surface summary to stdout
|
|
664
|
+
showInfo(
|
|
665
|
+
`Co-change: mode=${mode}, detected=${perStep.detection.modifications}, ` +
|
|
666
|
+
`injected=${perStep.injection.modifications}, warnings=${warnings.length}`
|
|
667
|
+
);
|
|
668
|
+
for (const w of warnings) {
|
|
669
|
+
showWarning(w);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Save report for PR body attachment after push
|
|
673
|
+
coChangeReport = report;
|
|
674
|
+
|
|
675
|
+
if (modifiedFiles.length === 0) {
|
|
676
|
+
showInfo('Co-change pipeline produced no Library changes');
|
|
677
|
+
} else {
|
|
678
|
+
const root = getRepoRoot();
|
|
679
|
+
const absFiles = modifiedFiles.map(f => path.isAbsolute(f) ? f : path.join(root, f));
|
|
680
|
+
const stageResult = stageFiles(absFiles);
|
|
681
|
+
|
|
682
|
+
if (!stageResult.success) {
|
|
683
|
+
showWarning(`Failed to stage co-change files: ${stageResult.error}`);
|
|
684
|
+
} else {
|
|
685
|
+
const windowDesc = tagName ? `${tagName}..HEAD` : 'full history';
|
|
686
|
+
const libCommitMsg = `chore(library): co-change correlations for ${windowDesc}`;
|
|
687
|
+
const libCommit = createCommit(libCommitMsg);
|
|
688
|
+
|
|
689
|
+
if (!libCommit.success) {
|
|
690
|
+
showWarning(`Failed to commit co-change files: ${libCommit.error}`);
|
|
691
|
+
} else {
|
|
692
|
+
showSuccess(`Co-change committed: ${libCommitMsg} (${modifiedFiles.length} file(s))`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
} catch (err) {
|
|
697
|
+
logger.warning(`co-change pipeline skipped: ${err.message}`);
|
|
698
|
+
logger.debug('back-merge', 'Co-change pipeline skipped', { error: err.message });
|
|
699
|
+
}
|
|
700
|
+
|
|
653
701
|
// 15. Verify sync
|
|
654
702
|
try {
|
|
655
703
|
const postMergeDivergence = getDivergence(opts.into, `origin/${opts.from}`);
|
|
@@ -719,18 +767,50 @@ export async function runBackMerge(args) {
|
|
|
719
767
|
await _revertFollowup(rcBranchForLog, repoRoot);
|
|
720
768
|
}
|
|
721
769
|
|
|
722
|
-
// 18b.
|
|
723
|
-
if (pushStatus === 'pushed') {
|
|
770
|
+
// 18b. Attach co-change report to PR body (AUT-3777)
|
|
771
|
+
if (coChangeReport && pushStatus === 'pushed') {
|
|
724
772
|
try {
|
|
725
|
-
const {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
773
|
+
const { parseGitHubRepo } = await import('../utils/github-client.js');
|
|
774
|
+
const { findExistingPR, updatePullRequestBody } = await import('../utils/github-api.js');
|
|
775
|
+
const repoInfo = parseGitHubRepo();
|
|
776
|
+
|
|
777
|
+
const existingPR = await findExistingPR({
|
|
778
|
+
owner: repoInfo.owner,
|
|
779
|
+
repo: repoInfo.repo,
|
|
780
|
+
head: opts.from,
|
|
781
|
+
base: opts.into,
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
if (existingPR) {
|
|
785
|
+
const OPEN_MARKER = '<!-- LIBRARY_COCHANGE_REPORT -->';
|
|
786
|
+
const CLOSE_MARKER = '<!-- /LIBRARY_COCHANGE_REPORT -->';
|
|
787
|
+
const section = `${OPEN_MARKER}\n${coChangeReport}\n${CLOSE_MARKER}`;
|
|
788
|
+
const markerRegex = new RegExp(
|
|
789
|
+
`${OPEN_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${CLOSE_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
let newBody;
|
|
793
|
+
if (markerRegex.test(existingPR.body || '')) {
|
|
794
|
+
// Re-run: replace existing section
|
|
795
|
+
newBody = (existingPR.body).replace(markerRegex, section);
|
|
796
|
+
} else {
|
|
797
|
+
// First run: append section
|
|
798
|
+
newBody = `${existingPR.body || ''}\n\n${section}`;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
await updatePullRequestBody(repoInfo.owner, repoInfo.repo, existingPR.number, newBody);
|
|
802
|
+
showSuccess('Co-change report attached to PR body');
|
|
803
|
+
} else {
|
|
804
|
+
logger.debug('back-merge', 'No existing PR found for report attachment', {
|
|
805
|
+
head: opts.from,
|
|
806
|
+
base: opts.into,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
} catch (prErr) {
|
|
810
|
+
logger.debug('back-merge', 'Could not attach co-change report to PR', {
|
|
811
|
+
error: prErr.message,
|
|
812
|
+
});
|
|
732
813
|
}
|
|
733
|
-
console.log('');
|
|
734
814
|
}
|
|
735
815
|
|
|
736
816
|
// 19. Summary
|
|
@@ -54,6 +54,10 @@ import {
|
|
|
54
54
|
} from '../utils/interactive-ui.js';
|
|
55
55
|
import logger from '../utils/logger.js';
|
|
56
56
|
import { colors, error, checkGitRepo } from './helpers.js';
|
|
57
|
+
import {
|
|
58
|
+
CONSOLE_WARNING_TEMPLATE,
|
|
59
|
+
LIBRARY_VERIFY_SKIPPED_WARNING
|
|
60
|
+
} from '../messages/library-warnings.js';
|
|
57
61
|
|
|
58
62
|
/**
|
|
59
63
|
* Validates prerequisites before version bump
|
|
@@ -374,6 +378,25 @@ function restoreSnapshot(snapshot) {
|
|
|
374
378
|
});
|
|
375
379
|
}
|
|
376
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Emit a loud Library staleness warning to stderr.
|
|
383
|
+
* Content comes from the wording constant + verify result data.
|
|
384
|
+
* Formatting uses ANSI codes for visual emphasis on TTY stderr.
|
|
385
|
+
*
|
|
386
|
+
* @param {import('../../.library/librarian/index.js').VerifyResult} result
|
|
387
|
+
* @private
|
|
388
|
+
*/
|
|
389
|
+
function _emitLibraryWarning(result) {
|
|
390
|
+
const y = colors.yellow;
|
|
391
|
+
const r = colors.reset;
|
|
392
|
+
const bar = `${y}${'='.repeat(63)}${r}`;
|
|
393
|
+
const content = CONSOLE_WARNING_TEMPLATE(result, { autoRegen: 'deferred' });
|
|
394
|
+
|
|
395
|
+
const lines = ['', bar, '', `${y}${content}${r}`, '', bar, ''];
|
|
396
|
+
|
|
397
|
+
process.stderr.write(lines.join('\n'));
|
|
398
|
+
}
|
|
399
|
+
|
|
377
400
|
/**
|
|
378
401
|
* Bump version command
|
|
379
402
|
* @param {Array<string>} args - Command arguments
|
|
@@ -441,6 +464,22 @@ export async function runBumpVersion(args) {
|
|
|
441
464
|
|
|
442
465
|
showSuccess('Prerequisites validated');
|
|
443
466
|
|
|
467
|
+
// Library verification gate — silent on clean, loud-warn on stale, never-hard-fail
|
|
468
|
+
logger.debug('bump-version', 'Running Library verification gate');
|
|
469
|
+
try {
|
|
470
|
+
const { verify } = await import('../../.library/librarian/index.js');
|
|
471
|
+
const verifyResult = await verify();
|
|
472
|
+
|
|
473
|
+
if (verifyResult.clean) {
|
|
474
|
+
logger.debug('bump-version', 'Library is clean — no warning needed');
|
|
475
|
+
} else {
|
|
476
|
+
_emitLibraryWarning(verifyResult);
|
|
477
|
+
}
|
|
478
|
+
} catch (verifyErr) {
|
|
479
|
+
const msg = `\n${colors.yellow} ${LIBRARY_VERIFY_SKIPPED_WARNING} ${verifyErr.message}${colors.reset}\n\n`;
|
|
480
|
+
process.stderr.write(msg);
|
|
481
|
+
}
|
|
482
|
+
|
|
444
483
|
// Step 2: Discover all version files
|
|
445
484
|
logger.debug('bump-version', 'Step 2: Discovering version files');
|
|
446
485
|
const discovery = discoverVersionFiles();
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
showWarning,
|
|
23
23
|
promptConfirmation
|
|
24
24
|
} from '../utils/interactive-ui.js';
|
|
25
|
-
import { getBranchPushStatus, pushBranch } from '../utils/git-operations.js';
|
|
25
|
+
import { getBranchPushStatus, pushBranch, stageFiles, createCommit, getRepoRoot } from '../utils/git-operations.js';
|
|
26
26
|
import logger from '../utils/logger.js';
|
|
27
27
|
import { resolveLabels } from '../utils/label-resolver.js';
|
|
28
28
|
import { CostTracker } from '../utils/cost-tracker.js';
|
|
@@ -664,6 +664,78 @@ export async function runCreatePr(args) {
|
|
|
664
664
|
logger.debug('create-pr', 'No unpushed tags found, continuing');
|
|
665
665
|
}
|
|
666
666
|
|
|
667
|
+
// Step 5.8: Library maintenance pipeline (AUT-3764)
|
|
668
|
+
logger.debug('create-pr', 'Step 5.8: Running Library maintenance pipeline');
|
|
669
|
+
try {
|
|
670
|
+
showInfo('Running Library maintenance pipeline...');
|
|
671
|
+
const { createPrPipeline } = await import('../../.library/librarian/index.js');
|
|
672
|
+
const root = getRepoRoot();
|
|
673
|
+
|
|
674
|
+
const pipelineSummary = await createPrPipeline({ repoRoot: root });
|
|
675
|
+
const {
|
|
676
|
+
modifiedFiles: libraryFiles,
|
|
677
|
+
perStep,
|
|
678
|
+
pendingDueToApiDown,
|
|
679
|
+
warnings: pipelineWarnings,
|
|
680
|
+
} = pipelineSummary;
|
|
681
|
+
|
|
682
|
+
// Surface pipeline summary
|
|
683
|
+
logger.debug('create-pr', 'Pipeline completed', {
|
|
684
|
+
modifiedCount: libraryFiles.length,
|
|
685
|
+
pendingDueToApiDown,
|
|
686
|
+
warningCount: pipelineWarnings.length,
|
|
687
|
+
});
|
|
688
|
+
showInfo(`Staleness: ${perStep.staleness.staleCount} stale, ${perStep.staleness.unbookedCount} unbooked`);
|
|
689
|
+
if (perStep.regen.changed > 0) {
|
|
690
|
+
showInfo(`Regenerated: ${perStep.regen.changed} book(s)`);
|
|
691
|
+
}
|
|
692
|
+
if (perStep.addRemoveRename.created > 0) {
|
|
693
|
+
showInfo(`Created: ${perStep.addRemoveRename.created} new book(s)`);
|
|
694
|
+
}
|
|
695
|
+
if (pendingDueToApiDown > 0) {
|
|
696
|
+
showWarning(`Gotchas pending (API down): ${pendingDueToApiDown}`);
|
|
697
|
+
}
|
|
698
|
+
for (const w of pipelineWarnings) {
|
|
699
|
+
showWarning(w);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (libraryFiles.length === 0) {
|
|
703
|
+
showInfo('Library already in sync');
|
|
704
|
+
} else {
|
|
705
|
+
// Stage all modified Library files (stageFiles expects absolute paths)
|
|
706
|
+
const absFiles = libraryFiles.map(f => path.join(root, f));
|
|
707
|
+
const stageResult = stageFiles(absFiles);
|
|
708
|
+
|
|
709
|
+
if (!stageResult.success) {
|
|
710
|
+
showWarning(`Failed to stage Library files: ${stageResult.error}`);
|
|
711
|
+
} else {
|
|
712
|
+
// Commit with deterministic message — separate commit, not amend
|
|
713
|
+
const libraryCommitMsg = `chore(library): sync books for ${currentBranch}`;
|
|
714
|
+
const commitResult = createCommit(libraryCommitMsg);
|
|
715
|
+
|
|
716
|
+
if (!commitResult.success) {
|
|
717
|
+
showWarning(`Failed to commit Library changes: ${commitResult.error}`);
|
|
718
|
+
} else {
|
|
719
|
+
showSuccess(`Library committed: ${libraryCommitMsg} (${libraryFiles.length} file(s))`);
|
|
720
|
+
|
|
721
|
+
// Push the Library commit to the PR branch's remote
|
|
722
|
+
const libraryPushResult = pushBranch(currentBranch);
|
|
723
|
+
if (!libraryPushResult.success) {
|
|
724
|
+
showWarning(`Failed to push Library commit: ${libraryPushResult.error}`);
|
|
725
|
+
} else {
|
|
726
|
+
showSuccess('Library commit pushed');
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
} catch (pipelineErr) {
|
|
732
|
+
// Pipeline failure is non-blocking — log and continue with PR creation
|
|
733
|
+
logger.warning('create-pr', 'Library pipeline failed, continuing', {
|
|
734
|
+
error: pipelineErr.message,
|
|
735
|
+
});
|
|
736
|
+
showWarning(`Library pipeline unavailable: ${pipelineErr.message}`);
|
|
737
|
+
}
|
|
738
|
+
|
|
667
739
|
// Step 6: Generate PR metadata using engine
|
|
668
740
|
logger.debug('create-pr', 'Step 6: Generating PR metadata with engine');
|
|
669
741
|
showInfo('Generating PR metadata with Claude...');
|
|
@@ -765,38 +837,6 @@ export async function runCreatePr(args) {
|
|
|
765
837
|
finalBody = `> ⚠️ This PR must be merged with **merge commit** (not squash)\n\n${prBody}`;
|
|
766
838
|
}
|
|
767
839
|
|
|
768
|
-
// Library staleness section — informational, non-blocking
|
|
769
|
-
try {
|
|
770
|
-
showInfo('📚 Checking library staleness...');
|
|
771
|
-
const { checkBook } = await import('../../.library/tools/staleness.js');
|
|
772
|
-
const { getBooksDir } = await import('../../.library/paths.js');
|
|
773
|
-
const { getRepoRoot } = await import('../utils/git-operations.js');
|
|
774
|
-
const booksDir = getBooksDir();
|
|
775
|
-
const root = getRepoRoot();
|
|
776
|
-
const changedSourceFiles = (filesArray || [])
|
|
777
|
-
.map(f => f.path || f)
|
|
778
|
-
.filter(p => p.startsWith('lib/') && p.endsWith('.js'));
|
|
779
|
-
const staleBooks = [];
|
|
780
|
-
for (const srcPath of changedSourceFiles) {
|
|
781
|
-
const bookName = `${srcPath.replace(/^lib\/.*\//, '').replace(/\.js$/, '')}.md`;
|
|
782
|
-
try {
|
|
783
|
-
const result = await checkBook(path.join(booksDir, bookName), root);
|
|
784
|
-
if (result.status === 'stale') staleBooks.push(bookName);
|
|
785
|
-
} catch {
|
|
786
|
-
// skip
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
if (staleBooks.length > 0) {
|
|
790
|
-
const bookList = staleBooks.map(b => `- \`${b}\``).join('\n');
|
|
791
|
-
finalBody += `\n\n---\n\n### 📚 Library Staleness\n\nThe following library books may need regeneration:\n\n${bookList}\n\nRun: \`npm run library:regenerate\``;
|
|
792
|
-
showWarning(`📚 ${staleBooks.length} stale book(s) — section added to PR body`);
|
|
793
|
-
} else if (changedSourceFiles.length > 0) {
|
|
794
|
-
showSuccess('📚 Library books are current');
|
|
795
|
-
}
|
|
796
|
-
} catch {
|
|
797
|
-
showWarning('📚 Library staleness check unavailable — .library/ tools not found');
|
|
798
|
-
}
|
|
799
|
-
|
|
800
840
|
// Step 10: Get reviewers from team + config fallback
|
|
801
841
|
logger.debug('create-pr', 'Step 10: Selecting reviewers via team resolution');
|
|
802
842
|
const reviewers = await selectReviewers({
|