claude-git-hooks 2.44.0 → 2.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/lib/commands/analyze-pr.js +9 -17
- package/lib/commands/help.js +15 -0
- package/lib/config.js +19 -91
- package/lib/defaults.json +130 -0
- package/lib/utils/analysis-engine.js +7 -3
- package/lib/utils/claude-client.js +9 -5
- package/lib/utils/config-registry.js +257 -0
- package/lib/utils/diff-analysis-orchestrator.js +13 -8
- package/lib/utils/judge.js +7 -4
- package/lib/utils/linear-connector.js +6 -4
- package/lib/utils/linter-runner.js +16 -1
- package/lib/utils/pr-metadata-engine.js +11 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
|
|
|
5
5
|
El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.45.0] - 2026-05-13
|
|
9
|
+
|
|
10
|
+
### ✨ Added
|
|
11
|
+
- Added centralized config-registry module with local defaults and remote override support (SUE-138)
|
|
12
|
+
- Added lib/defaults.json as the single source of truth for all package default values (SUE-138)
|
|
13
|
+
|
|
14
|
+
### 🔧 Changed
|
|
15
|
+
- Replaced inline hardcoded constants across modules with config-registry lookups — judge, orchestrator, Linear connector, model aliases, linter tools, and PR analysis categories now read from defaults.json with remote override capability (SUE-138)
|
|
16
|
+
- Updated config merge priority to HARDCODED < remote settings.json < defaults < preset < user overrides (SUE-138)
|
|
17
|
+
- Added config-registry mocks to unit tests for modules consuming centralized defaults (SUE-138)
|
|
18
|
+
|
|
19
|
+
### 🐛 Fixed
|
|
20
|
+
- Fixed Linear ticket fetch issue (#155)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## [2.44.1] - 2026-05-13
|
|
24
|
+
|
|
25
|
+
### ✨ Added
|
|
26
|
+
- Added unit tests for linear-connector module covering ticket extraction, parsing, token loading, connection testing, and ticket fetching (SUE-154)
|
|
27
|
+
|
|
28
|
+
### 🐛 Fixed
|
|
29
|
+
- Fixed help command to cap book reads at 5 in Pass 2, preventing oversized prompts when the LLM requests too many library books (SUE-154)
|
|
30
|
+
|
|
31
|
+
### 🗑️ Removed
|
|
32
|
+
- Removed CLAUDE-MIGRATION.md — migration map no longer needed after library stabilization
|
|
33
|
+
|
|
34
|
+
|
|
8
35
|
## [2.44.0] - 2026-05-04
|
|
9
36
|
|
|
10
37
|
### ✨ Added
|
|
@@ -33,6 +33,7 @@ import { promptConfirmation, promptMenu } from '../utils/interactive-ui.js';
|
|
|
33
33
|
import { CostTracker } from '../utils/cost-tracker.js';
|
|
34
34
|
import { colors, error, fatal, info, warning, checkGitRepo } from './helpers.js';
|
|
35
35
|
import logger from '../utils/logger.js';
|
|
36
|
+
import { resolveSection } from '../utils/config-registry.js';
|
|
36
37
|
import path from 'path';
|
|
37
38
|
|
|
38
39
|
// ─── JSON Error Helper ────────────────────────────────────────────────────
|
|
@@ -380,23 +381,14 @@ export async function runAnalyzePr(args) {
|
|
|
380
381
|
warning(`Could not load preset "${presetName}" guidelines, using defaults`);
|
|
381
382
|
}
|
|
382
383
|
|
|
383
|
-
// Step 7: Build category strings from config
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
'ticket-alignment',
|
|
392
|
-
'scope',
|
|
393
|
-
'style',
|
|
394
|
-
'good-practice',
|
|
395
|
-
'extensibility',
|
|
396
|
-
'observability',
|
|
397
|
-
'documentation',
|
|
398
|
-
'testing'
|
|
399
|
-
];
|
|
384
|
+
// Step 7: Build category strings from config (remote categories.json > local defaults)
|
|
385
|
+
// Categories are guaranteed by defaults.json loaded at import time via config-registry.
|
|
386
|
+
// The || [] fallback guards against unexpected config corruption or a missing section.
|
|
387
|
+
const resolvedPrAnalysis = await resolveSection('prAnalysis');
|
|
388
|
+
const inlineCategories = resolvedPrAnalysis?.inlineCategories ||
|
|
389
|
+
config.prAnalysis?.inlineCategories || [];
|
|
390
|
+
const generalCategories = resolvedPrAnalysis?.generalCategories ||
|
|
391
|
+
config.prAnalysis?.generalCategories || [];
|
|
400
392
|
const allCategories = [...inlineCategories, ...generalCategories];
|
|
401
393
|
|
|
402
394
|
const inlineCategoriesStr = inlineCategories.map((c) => `- \`${c}\``).join('\n');
|
package/lib/commands/help.js
CHANGED
|
@@ -163,6 +163,13 @@ function printAiResponse(response, source) {
|
|
|
163
163
|
/** Package root directory — resolved once from __dirname */
|
|
164
164
|
const _packageRoot = path.join(__dirname, '..', '..');
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Maximum number of books to read in Pass 2.
|
|
168
|
+
* Why: Matches the "do not request more than 5" instruction in HELP_NAVIGATE.md.
|
|
169
|
+
* Caps LLM-sourced lists to prevent oversized prompts that would fail the second call.
|
|
170
|
+
*/
|
|
171
|
+
const MAX_BOOKS = 5;
|
|
172
|
+
|
|
166
173
|
/**
|
|
167
174
|
* Catalog directories within .library/ to auto-discover
|
|
168
175
|
* Why: These contain index and shelf files that form the navigational catalog.
|
|
@@ -346,6 +353,14 @@ async function readAndAnswerWithBooks(booksLine, question, catalog) {
|
|
|
346
353
|
return null;
|
|
347
354
|
}
|
|
348
355
|
|
|
356
|
+
if (bookPaths.length > MAX_BOOKS) {
|
|
357
|
+
logger.debug('help - readAndAnswerWithBooks', 'Capping book list', {
|
|
358
|
+
requested: bookPaths.length,
|
|
359
|
+
max: MAX_BOOKS
|
|
360
|
+
});
|
|
361
|
+
bookPaths.length = MAX_BOOKS;
|
|
362
|
+
}
|
|
363
|
+
|
|
349
364
|
logger.debug('help - readAndAnswerWithBooks', 'Reading books from disk', { bookPaths });
|
|
350
365
|
|
|
351
366
|
// Read books from local filesystem in parallel
|
package/lib/config.js
CHANGED
|
@@ -24,104 +24,23 @@
|
|
|
24
24
|
import fs from 'fs';
|
|
25
25
|
import path from 'path';
|
|
26
26
|
import logger from './utils/logger.js';
|
|
27
|
+
import { getDefaults, resolveSection } from './utils/config-registry.js';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
-
* These are NOT user-configurable
|
|
30
|
+
* Package defaults loaded from lib/defaults.json via config-registry.
|
|
31
|
+
* These are NOT user-configurable — sensible defaults that work for everyone.
|
|
32
|
+
* See lib/defaults.json for the full structure and values.
|
|
31
33
|
*/
|
|
32
|
-
const HARDCODED =
|
|
33
|
-
analysis: {
|
|
34
|
-
maxFileSize: 1000000, // 1MB - sufficient for most files
|
|
35
|
-
maxFiles: 30, // Reasonable limit per commit
|
|
36
|
-
timeout: 360000, // 6 minutes - adequate for Claude API
|
|
37
|
-
contextLines: 3, // Git default
|
|
38
|
-
ignoreExtensions: [] // Can be set in advanced config only
|
|
39
|
-
},
|
|
40
|
-
commitMessage: {
|
|
41
|
-
autoKeyword: 'auto', // Standard keyword
|
|
42
|
-
timeout: 300000, // Use same timeout as analysis
|
|
43
|
-
taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})' // Jira/GitHub/Linear pattern
|
|
44
|
-
},
|
|
45
|
-
subagents: {
|
|
46
|
-
enabled: true // Enable by default (faster analysis via orchestration)
|
|
47
|
-
},
|
|
48
|
-
templates: {
|
|
49
|
-
baseDir: '.claude/prompts',
|
|
50
|
-
analysis: 'CLAUDE_ANALYSIS_PROMPT.md',
|
|
51
|
-
guidelines: 'CLAUDE_PRE_COMMIT.md',
|
|
52
|
-
commitMessage: 'COMMIT_MESSAGE.md',
|
|
53
|
-
analyzeDiff: 'ANALYZE_DIFF.md',
|
|
54
|
-
resolution: 'CLAUDE_RESOLUTION_PROMPT.md',
|
|
55
|
-
createGithubPR: 'CREATE_GITHUB_PR.md'
|
|
56
|
-
},
|
|
57
|
-
output: {
|
|
58
|
-
outputDir: '.claude/out',
|
|
59
|
-
debugFile: '.claude/out/debug-claude-response.json',
|
|
60
|
-
resolutionFile: '.claude/out/claude_resolution_prompt.md',
|
|
61
|
-
prAnalysisFile: '.claude/out/pr-analysis.json'
|
|
62
|
-
},
|
|
63
|
-
system: {
|
|
64
|
-
debug: false, // Controlled by --debug flag
|
|
65
|
-
wslCheckTimeout: 15000 // System behavior
|
|
66
|
-
},
|
|
67
|
-
git: {
|
|
68
|
-
diffFilter: 'ACM' // Standard: Added, Copied, Modified
|
|
69
|
-
},
|
|
70
|
-
github: {
|
|
71
|
-
enabled: true // Always enabled
|
|
72
|
-
},
|
|
73
|
-
prAnalysis: {
|
|
74
|
-
model: 'sonnet',
|
|
75
|
-
timeout: 300000, // 5 minutes
|
|
76
|
-
inlineCategories: ['bug', 'security', 'performance', 'hotspot'],
|
|
77
|
-
generalCategories: [
|
|
78
|
-
'ticket-alignment',
|
|
79
|
-
'scope',
|
|
80
|
-
'style',
|
|
81
|
-
'good-practice',
|
|
82
|
-
'extensibility',
|
|
83
|
-
'observability',
|
|
84
|
-
'documentation',
|
|
85
|
-
'testing'
|
|
86
|
-
]
|
|
87
|
-
},
|
|
88
|
-
linting: {
|
|
89
|
-
enabled: true, // Run linters before Claude analysis
|
|
90
|
-
autoFix: true, // Auto-fix and re-stage
|
|
91
|
-
failOnError: true, // Block commit on linting errors
|
|
92
|
-
failOnWarning: false, // Do not block on warnings
|
|
93
|
-
timeout: 30000 // 30s per linter
|
|
94
|
-
},
|
|
95
|
-
claude: {
|
|
96
|
-
defaultModel: 'sonnet' // Fallback model for SDK headless mode
|
|
97
|
-
}
|
|
98
|
-
};
|
|
34
|
+
const HARDCODED = getDefaults();
|
|
99
35
|
|
|
100
36
|
/**
|
|
101
37
|
* Default user-configurable values (v2.8.0)
|
|
38
|
+
* Derived from the github.pr section of HARDCODED.
|
|
102
39
|
* Only these can be overridden in .claude/config.json
|
|
103
40
|
*/
|
|
104
41
|
const defaults = {
|
|
105
|
-
// GitHub PR configuration (user-specific)
|
|
106
42
|
github: {
|
|
107
|
-
pr:
|
|
108
|
-
defaultBase: 'develop', // Project default branch
|
|
109
|
-
reviewers: [], // Project reviewers
|
|
110
|
-
labelRules: {
|
|
111
|
-
// Labels by preset
|
|
112
|
-
backend: ['backend', 'java'],
|
|
113
|
-
frontend: ['frontend', 'react'],
|
|
114
|
-
fullstack: ['fullstack'],
|
|
115
|
-
database: ['database', 'sql'],
|
|
116
|
-
ai: ['ai', 'tooling'],
|
|
117
|
-
default: []
|
|
118
|
-
},
|
|
119
|
-
// Auto-push configuration (v2.11.0)
|
|
120
|
-
autoPush: true, // Auto-push unpublished branches
|
|
121
|
-
pushConfirm: true, // Prompt for confirmation before push
|
|
122
|
-
verifyRemote: true, // Verify remote exists before push
|
|
123
|
-
showCommits: true // Show commit preview before push
|
|
124
|
-
}
|
|
43
|
+
pr: structuredClone(HARDCODED.github.pr)
|
|
125
44
|
}
|
|
126
45
|
};
|
|
127
46
|
|
|
@@ -132,7 +51,7 @@ const defaults = {
|
|
|
132
51
|
* - v2.8.0: { version: "2.8.0", preset: "...", overrides: {...} }
|
|
133
52
|
* - Legacy: { preset: "...", analysis: {...}, ... } (auto-migrates with warning)
|
|
134
53
|
*
|
|
135
|
-
* Merge priority: HARDCODED < defaults < preset config < user overrides
|
|
54
|
+
* Merge priority: HARDCODED < remote settings.json < defaults < preset config < user overrides
|
|
136
55
|
*
|
|
137
56
|
* @param {string} baseDir - Base directory to search for config (default: cwd)
|
|
138
57
|
* @returns {Promise<Object>} Merged configuration
|
|
@@ -194,8 +113,17 @@ const loadUserConfig = async (baseDir = process.cwd()) => {
|
|
|
194
113
|
}
|
|
195
114
|
}
|
|
196
115
|
|
|
197
|
-
//
|
|
198
|
-
const
|
|
116
|
+
// Fetch remote settings.json overrides for team-policy sections
|
|
117
|
+
const remoteSettings = {};
|
|
118
|
+
const settingsSections = ['analysis', 'commitMessage', 'linting'];
|
|
119
|
+
const sectionResults = await Promise.all(settingsSections.map((s) => resolveSection(s)));
|
|
120
|
+
settingsSections.forEach((section, i) => {
|
|
121
|
+
if (sectionResults[i]) remoteSettings[section] = sectionResults[i];
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Merge priority: HARDCODED < remote settings.json < defaults < preset < user overrides
|
|
125
|
+
const withRemote = deepMerge(HARDCODED, remoteSettings);
|
|
126
|
+
const baseConfig = deepMerge(withRemote, defaults);
|
|
199
127
|
const withPreset = deepMerge(baseConfig, presetConfig);
|
|
200
128
|
const final = deepMerge(withPreset, userOverrides);
|
|
201
129
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"analysis": {
|
|
3
|
+
"maxFileSize": 1000000,
|
|
4
|
+
"maxFiles": 30,
|
|
5
|
+
"timeout": 360000,
|
|
6
|
+
"contextLines": 3,
|
|
7
|
+
"ignoreExtensions": []
|
|
8
|
+
},
|
|
9
|
+
"commitMessage": {
|
|
10
|
+
"autoKeyword": "auto",
|
|
11
|
+
"timeout": 300000,
|
|
12
|
+
"taskIdPattern": "([A-Z]{1,3}[-\\s]\\d{3,5})"
|
|
13
|
+
},
|
|
14
|
+
"subagents": {
|
|
15
|
+
"enabled": true
|
|
16
|
+
},
|
|
17
|
+
"templates": {
|
|
18
|
+
"baseDir": ".claude/prompts",
|
|
19
|
+
"analysis": "CLAUDE_ANALYSIS_PROMPT.md",
|
|
20
|
+
"guidelines": "CLAUDE_PRE_COMMIT.md",
|
|
21
|
+
"commitMessage": "COMMIT_MESSAGE.md",
|
|
22
|
+
"analyzeDiff": "ANALYZE_DIFF.md",
|
|
23
|
+
"resolution": "CLAUDE_RESOLUTION_PROMPT.md",
|
|
24
|
+
"createGithubPR": "CREATE_GITHUB_PR.md"
|
|
25
|
+
},
|
|
26
|
+
"output": {
|
|
27
|
+
"outputDir": ".claude/out",
|
|
28
|
+
"debugFile": ".claude/out/debug-claude-response.json",
|
|
29
|
+
"resolutionFile": ".claude/out/claude_resolution_prompt.md",
|
|
30
|
+
"prAnalysisFile": ".claude/out/pr-analysis.json"
|
|
31
|
+
},
|
|
32
|
+
"system": {
|
|
33
|
+
"debug": false,
|
|
34
|
+
"wslCheckTimeout": 15000
|
|
35
|
+
},
|
|
36
|
+
"git": {
|
|
37
|
+
"diffFilter": "ACM"
|
|
38
|
+
},
|
|
39
|
+
"github": {
|
|
40
|
+
"enabled": true,
|
|
41
|
+
"pr": {
|
|
42
|
+
"defaultBase": "develop",
|
|
43
|
+
"reviewers": [],
|
|
44
|
+
"labelRules": {
|
|
45
|
+
"backend": ["backend", "java"],
|
|
46
|
+
"frontend": ["frontend", "react"],
|
|
47
|
+
"fullstack": ["fullstack"],
|
|
48
|
+
"database": ["database", "sql"],
|
|
49
|
+
"ai": ["ai", "tooling"],
|
|
50
|
+
"default": []
|
|
51
|
+
},
|
|
52
|
+
"autoPush": true,
|
|
53
|
+
"pushConfirm": true,
|
|
54
|
+
"verifyRemote": true,
|
|
55
|
+
"showCommits": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"prAnalysis": {
|
|
59
|
+
"model": "sonnet",
|
|
60
|
+
"timeout": 300000,
|
|
61
|
+
"inlineCategories": ["bug", "security", "performance", "hotspot"],
|
|
62
|
+
"generalCategories": [
|
|
63
|
+
"ticket-alignment",
|
|
64
|
+
"scope",
|
|
65
|
+
"style",
|
|
66
|
+
"good-practice",
|
|
67
|
+
"extensibility",
|
|
68
|
+
"observability",
|
|
69
|
+
"documentation",
|
|
70
|
+
"testing"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"linting": {
|
|
74
|
+
"enabled": true,
|
|
75
|
+
"autoFix": true,
|
|
76
|
+
"failOnError": true,
|
|
77
|
+
"failOnWarning": false,
|
|
78
|
+
"timeout": 30000
|
|
79
|
+
},
|
|
80
|
+
"claude": {
|
|
81
|
+
"defaultModel": "sonnet"
|
|
82
|
+
},
|
|
83
|
+
"models": {
|
|
84
|
+
"modelMap": {
|
|
85
|
+
"haiku": "claude-haiku-4-5-20251001",
|
|
86
|
+
"sonnet": "claude-sonnet-4-6",
|
|
87
|
+
"opus": "claude-opus-4-6"
|
|
88
|
+
},
|
|
89
|
+
"defaults": {
|
|
90
|
+
"analysis": "sonnet",
|
|
91
|
+
"orchestrator": "opus",
|
|
92
|
+
"judge": "sonnet",
|
|
93
|
+
"prAnalysis": "sonnet",
|
|
94
|
+
"commitMessage": "sonnet"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"tools": {
|
|
98
|
+
"prettier": { "enabled": true, "timeout": 30000 },
|
|
99
|
+
"eslint": { "enabled": true, "timeout": 30000 },
|
|
100
|
+
"spotless": { "enabled": true, "timeout": 120000, "perFile": true },
|
|
101
|
+
"sqlfluff": { "enabled": true, "timeout": 30000 }
|
|
102
|
+
},
|
|
103
|
+
"presetLinters": {
|
|
104
|
+
"frontend": ["prettier", "eslint"],
|
|
105
|
+
"backend": ["spotless"],
|
|
106
|
+
"fullstack": ["prettier", "eslint", "spotless"],
|
|
107
|
+
"database": ["sqlfluff"],
|
|
108
|
+
"ai": ["prettier", "eslint"],
|
|
109
|
+
"default": ["prettier", "eslint"]
|
|
110
|
+
},
|
|
111
|
+
"judge": {
|
|
112
|
+
"model": "sonnet",
|
|
113
|
+
"timeout": 360000
|
|
114
|
+
},
|
|
115
|
+
"orchestrator": {
|
|
116
|
+
"model": "opus",
|
|
117
|
+
"timeout": 60000,
|
|
118
|
+
"threshold": 3
|
|
119
|
+
},
|
|
120
|
+
"prMetadata": {
|
|
121
|
+
"timeout": 180000,
|
|
122
|
+
"maxDiffSize": 50000
|
|
123
|
+
},
|
|
124
|
+
"linear": {
|
|
125
|
+
"hostname": "api.linear.app",
|
|
126
|
+
"path": "/graphql",
|
|
127
|
+
"timeout": 10000,
|
|
128
|
+
"maxRetries": 2
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -35,6 +35,7 @@ import { orchestrateBatches } from './diff-analysis-orchestrator.js';
|
|
|
35
35
|
import { buildAnalysisPrompt } from './prompt-builder.js';
|
|
36
36
|
import logger from './logger.js';
|
|
37
37
|
import { recordMetric } from './metrics.js';
|
|
38
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Standard file data schema used throughout the analysis pipeline
|
|
@@ -360,7 +361,7 @@ export const displayResults = (result, { silent = false } = {}) => {
|
|
|
360
361
|
};
|
|
361
362
|
|
|
362
363
|
// Minimum file count to trigger Opus orchestration instead of sequential analysis
|
|
363
|
-
const ORCHESTRATOR_THRESHOLD =
|
|
364
|
+
const ORCHESTRATOR_THRESHOLD = getDefaultSection('orchestrator').threshold;
|
|
364
365
|
|
|
365
366
|
/**
|
|
366
367
|
* Runs code analysis on files
|
|
@@ -385,12 +386,15 @@ export const runAnalysis = async (filesData, config, options = {}) => {
|
|
|
385
386
|
return createEmptyResult();
|
|
386
387
|
}
|
|
387
388
|
|
|
388
|
-
|
|
389
|
+
// Resolve orchestrator threshold: remote settings.json > local defaults.json
|
|
390
|
+
const orchConfig = await resolveSection('orchestrator');
|
|
391
|
+
const threshold = orchConfig?.threshold || ORCHESTRATOR_THRESHOLD;
|
|
392
|
+
const useOrchestrator = filesData.length >= threshold;
|
|
389
393
|
|
|
390
394
|
logger.debug('analysis-engine - runAnalysis', 'Starting analysis', {
|
|
391
395
|
fileCount: filesData.length,
|
|
392
396
|
useOrchestrator,
|
|
393
|
-
threshold
|
|
397
|
+
threshold
|
|
394
398
|
});
|
|
395
399
|
|
|
396
400
|
let result;
|
|
@@ -21,6 +21,7 @@ import path from 'path';
|
|
|
21
21
|
import os from 'os';
|
|
22
22
|
import logger from './logger.js';
|
|
23
23
|
import config from '../config.js';
|
|
24
|
+
import { getDefaultSection } from './config-registry.js';
|
|
24
25
|
import {
|
|
25
26
|
detectClaudeError,
|
|
26
27
|
formatClaudeError,
|
|
@@ -47,14 +48,17 @@ class ClaudeClientError extends Error {
|
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Model alias map — resolves shorthand names to full SDK model IDs.
|
|
51
|
+
* Loaded from lib/defaults.json via config-registry, with env var overrides.
|
|
50
52
|
* Env var overrides match Lumiere contract (CLAUDE_MODEL_HAIKU, CLAUDE_MODEL_SONNET, CLAUDE_MODEL_OPUS).
|
|
51
53
|
* Full model IDs (e.g., 'claude-sonnet-4-6') pass through unchanged.
|
|
52
54
|
*/
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
55
|
+
const _localModelMap = getDefaultSection('models').modelMap;
|
|
56
|
+
const MODEL_ALIASES = Object.fromEntries(
|
|
57
|
+
Object.entries(_localModelMap).map(([alias, fullId]) => [
|
|
58
|
+
alias,
|
|
59
|
+
process.env[`CLAUDE_MODEL_${alias.toUpperCase()}`] || fullId
|
|
60
|
+
])
|
|
61
|
+
);
|
|
58
62
|
|
|
59
63
|
/**
|
|
60
64
|
* Resolves a model alias to a full model ID.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: config-registry.js
|
|
3
|
+
* Purpose: Unified configuration reader with local defaults + remote overrides
|
|
4
|
+
*
|
|
5
|
+
* Design:
|
|
6
|
+
* - Reads lib/defaults.json synchronously at import time (ships with the package)
|
|
7
|
+
* - Fetches remote overrides from git-hooks-config via remote-config.js (async, cached)
|
|
8
|
+
* - Merge priority: local defaults < remote overrides
|
|
9
|
+
* - Consumers higher in the chain (presets, user .claude/config.json) merge on top separately
|
|
10
|
+
*
|
|
11
|
+
* Section-to-remote-file mapping:
|
|
12
|
+
* models → models.json (root)
|
|
13
|
+
* tools → tools.json (.tools key)
|
|
14
|
+
* prAnalysis → categories.json (.inlineCategories, .generalCategories → merged into prAnalysis)
|
|
15
|
+
* presetLinters → formatters.json (.presetTools)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import { fileURLToPath } from 'url';
|
|
21
|
+
import logger from './logger.js';
|
|
22
|
+
import { fetchRemoteConfig } from './remote-config.js';
|
|
23
|
+
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = path.dirname(__filename);
|
|
26
|
+
|
|
27
|
+
/** Path to the package defaults file */
|
|
28
|
+
const DEFAULTS_PATH = path.resolve(__dirname, '..', 'defaults.json');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Helper: create a mapping entry that reads a section key from settings.json.
|
|
32
|
+
* @param {string} sectionKey - Key within settings.json (e.g., 'analysis', 'judge')
|
|
33
|
+
* @returns {{ file: string, extract: (remote: Object) => Object }}
|
|
34
|
+
*/
|
|
35
|
+
const _settingsEntry = (sectionKey) => ({
|
|
36
|
+
file: 'settings.json',
|
|
37
|
+
extract: (remote) => remote?.[sectionKey] || {}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Maps defaults.json section names to remote config files and their extraction logic.
|
|
42
|
+
* Each entry defines:
|
|
43
|
+
* - file: the remote JSON filename to fetch
|
|
44
|
+
* - extract: function that transforms the remote JSON into the shape expected for deep-merge
|
|
45
|
+
* @type {Object<string, { file: string, extract: (remote: Object) => Object }>}
|
|
46
|
+
*/
|
|
47
|
+
const SECTION_REMOTE_MAP = {
|
|
48
|
+
models: {
|
|
49
|
+
file: 'models.json',
|
|
50
|
+
extract: (remote) => remote
|
|
51
|
+
},
|
|
52
|
+
tools: {
|
|
53
|
+
file: 'tools.json',
|
|
54
|
+
extract: (remote) => remote?.tools || {}
|
|
55
|
+
},
|
|
56
|
+
prAnalysis: {
|
|
57
|
+
file: 'categories.json',
|
|
58
|
+
extract: (remote) => {
|
|
59
|
+
const result = {};
|
|
60
|
+
if (remote?.inlineCategories) result.inlineCategories = remote.inlineCategories;
|
|
61
|
+
if (remote?.generalCategories) result.generalCategories = remote.generalCategories;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
presetLinters: {
|
|
66
|
+
file: 'formatters.json',
|
|
67
|
+
extract: (remote) => remote?.presetTools || {}
|
|
68
|
+
},
|
|
69
|
+
// Team-policy sections — all read from settings.json
|
|
70
|
+
analysis: _settingsEntry('analysis'),
|
|
71
|
+
commitMessage: _settingsEntry('commitMessage'),
|
|
72
|
+
judge: _settingsEntry('judge'),
|
|
73
|
+
orchestrator: _settingsEntry('orchestrator'),
|
|
74
|
+
prMetadata: _settingsEntry('prMetadata'),
|
|
75
|
+
linting: _settingsEntry('linting')
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ── Sync: local defaults (read once at import time) ────────────────────────
|
|
79
|
+
|
|
80
|
+
let _defaults = null;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Load defaults.json synchronously. Called once at module init.
|
|
84
|
+
* @returns {Object} Parsed defaults
|
|
85
|
+
* @throws {Error} If defaults.json is missing or invalid (fatal — package is broken)
|
|
86
|
+
*/
|
|
87
|
+
function _loadDefaults() {
|
|
88
|
+
if (_defaults !== null) return _defaults;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const raw = fs.readFileSync(DEFAULTS_PATH, 'utf8');
|
|
92
|
+
_defaults = JSON.parse(raw);
|
|
93
|
+
logger.debug('config-registry', 'Loaded defaults.json', {
|
|
94
|
+
sections: Object.keys(_defaults)
|
|
95
|
+
});
|
|
96
|
+
return _defaults;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Failed to load package defaults from ${DEFAULTS_PATH}: ${err.message}. ` +
|
|
100
|
+
'This indicates a broken installation — reinstall claude-git-hooks.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Load immediately on import
|
|
106
|
+
_loadDefaults();
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns the full defaults object (sync, no I/O after init).
|
|
110
|
+
* @returns {Object} Complete defaults from lib/defaults.json
|
|
111
|
+
*/
|
|
112
|
+
export function getDefaults() {
|
|
113
|
+
return _defaults;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns a single section from defaults (sync).
|
|
118
|
+
* @param {string} sectionName - Top-level key in defaults.json (e.g., 'models', 'tools', 'judge')
|
|
119
|
+
* @returns {Object|undefined} The section value, or undefined if not found
|
|
120
|
+
*/
|
|
121
|
+
export function getDefaultSection(sectionName) {
|
|
122
|
+
return _defaults?.[sectionName];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Async: remote overrides (fetched on demand, cached) ────────────────────
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* In-memory cache for resolved sections (local + remote merged).
|
|
129
|
+
* @type {Map<string, Object>}
|
|
130
|
+
*/
|
|
131
|
+
const _resolvedCache = new Map();
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Deep-merge two objects (source overrides target).
|
|
135
|
+
* Arrays are replaced wholesale (not concatenated).
|
|
136
|
+
* @param {Object} target - Base object
|
|
137
|
+
* @param {Object} source - Override object
|
|
138
|
+
* @returns {Object} Merged result
|
|
139
|
+
*/
|
|
140
|
+
function _deepMerge(target, source) {
|
|
141
|
+
const result = { ...target };
|
|
142
|
+
for (const key in source) {
|
|
143
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
144
|
+
result[key] = _deepMerge(target[key] || {}, source[key]);
|
|
145
|
+
} else {
|
|
146
|
+
result[key] = source[key];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a config section by merging local defaults with remote overrides.
|
|
154
|
+
*
|
|
155
|
+
* If the section has a mapping in SECTION_REMOTE_MAP, the corresponding remote
|
|
156
|
+
* file is fetched and merged (remote > local). If fetch fails, local defaults
|
|
157
|
+
* are returned. Results are cached per process.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} sectionName - Top-level key in defaults.json
|
|
160
|
+
* @returns {Promise<Object>} Merged section (local defaults + remote overrides)
|
|
161
|
+
*/
|
|
162
|
+
export async function resolveSection(sectionName) {
|
|
163
|
+
if (_resolvedCache.has(sectionName)) {
|
|
164
|
+
return _resolvedCache.get(sectionName);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const localSection = getDefaultSection(sectionName);
|
|
168
|
+
if (localSection === undefined) {
|
|
169
|
+
logger.debug('config-registry - resolveSection', 'Section not found in defaults', {
|
|
170
|
+
sectionName
|
|
171
|
+
});
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const mapping = SECTION_REMOTE_MAP[sectionName];
|
|
176
|
+
if (!mapping) {
|
|
177
|
+
// No remote file for this section — return local as-is
|
|
178
|
+
_resolvedCache.set(sectionName, localSection);
|
|
179
|
+
return localSection;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const remoteData = await fetchRemoteConfig(mapping.file);
|
|
183
|
+
if (!remoteData) {
|
|
184
|
+
logger.debug('config-registry - resolveSection', 'Remote unavailable, using local defaults', {
|
|
185
|
+
sectionName,
|
|
186
|
+
remoteFile: mapping.file
|
|
187
|
+
});
|
|
188
|
+
_resolvedCache.set(sectionName, localSection);
|
|
189
|
+
return localSection;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const remoteOverrides = mapping.extract(remoteData);
|
|
193
|
+
const merged = _deepMerge(localSection, remoteOverrides);
|
|
194
|
+
|
|
195
|
+
logger.debug('config-registry - resolveSection', 'Merged local + remote', {
|
|
196
|
+
sectionName,
|
|
197
|
+
remoteFile: mapping.file,
|
|
198
|
+
localKeys: Object.keys(localSection),
|
|
199
|
+
remoteKeys: Object.keys(remoteOverrides)
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
_resolvedCache.set(sectionName, merged);
|
|
203
|
+
return merged;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Convenience resolvers ──────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve a model alias to a full model ID, with remote override support.
|
|
210
|
+
* Priority: env var > remote models.json > local defaults.json
|
|
211
|
+
*
|
|
212
|
+
* @param {string} alias - Model alias ('sonnet', 'opus', 'haiku') or full ID
|
|
213
|
+
* @returns {Promise<string>} Full model ID
|
|
214
|
+
*/
|
|
215
|
+
export async function resolveModelAlias(alias) {
|
|
216
|
+
const modelsConfig = await resolveSection('models');
|
|
217
|
+
const modelMap = modelsConfig?.modelMap || {};
|
|
218
|
+
|
|
219
|
+
// Env var override (highest priority)
|
|
220
|
+
const envKey = `CLAUDE_MODEL_${alias.toUpperCase()}`;
|
|
221
|
+
if (process.env[envKey]) {
|
|
222
|
+
return process.env[envKey];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return modelMap[alias] || alias;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Resolve configuration for a specific linter tool, with remote override support.
|
|
230
|
+
* Merges local defaults.tools[toolName] with remote tools.json tools[toolName].
|
|
231
|
+
*
|
|
232
|
+
* @param {string} toolName - Tool name ('prettier', 'eslint', 'spotless', 'sqlfluff')
|
|
233
|
+
* @returns {Promise<Object|undefined>} Tool config (enabled, timeout, perFile) or undefined
|
|
234
|
+
*/
|
|
235
|
+
export async function resolveToolConfig(toolName) {
|
|
236
|
+
const toolsConfig = await resolveSection('tools');
|
|
237
|
+
return toolsConfig?.[toolName];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Test helpers ───────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Reset all caches. Test helper — not intended for production use.
|
|
244
|
+
*/
|
|
245
|
+
export function _resetRegistry() {
|
|
246
|
+
_resolvedCache.clear();
|
|
247
|
+
_defaults = null;
|
|
248
|
+
_loadDefaults();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Reset resolved cache only (keeps defaults loaded).
|
|
253
|
+
* Useful for testing remote merge behavior without re-reading defaults.json.
|
|
254
|
+
*/
|
|
255
|
+
export function _resetResolvedCache() {
|
|
256
|
+
_resolvedCache.clear();
|
|
257
|
+
}
|
|
@@ -23,10 +23,11 @@ import path from 'path';
|
|
|
23
23
|
import { executeClaudeWithRetry, extractJSON } from './claude-client.js';
|
|
24
24
|
import { loadPrompt } from './prompt-builder.js';
|
|
25
25
|
import logger from './logger.js';
|
|
26
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
26
27
|
|
|
27
|
-
// Orchestration
|
|
28
|
-
const ORCHESTRATOR_MODEL =
|
|
29
|
-
|
|
28
|
+
// Orchestration config from lib/defaults.json (sync fallback)
|
|
29
|
+
const { model: ORCHESTRATOR_MODEL, timeout: ORCHESTRATOR_TIMEOUT } =
|
|
30
|
+
getDefaultSection('orchestrator');
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Counts added/removed lines in a unified diff string
|
|
@@ -184,6 +185,10 @@ const _buildCommonContext = (filesData, batchGroups) => {
|
|
|
184
185
|
* @returns {Promise<{batches: Array<{files: FileData[], rationale: string, model: string}>, commonContext: string}>}
|
|
185
186
|
*/
|
|
186
187
|
export const orchestrateBatches = async (filesData, { headless = false } = {}) => {
|
|
188
|
+
// Resolve orchestrator config: remote settings.json > local defaults.json
|
|
189
|
+
const orchConfig = await resolveSection('orchestrator');
|
|
190
|
+
const orchModel = orchConfig?.model || ORCHESTRATOR_MODEL;
|
|
191
|
+
const orchTimeout = orchConfig?.timeout || ORCHESTRATOR_TIMEOUT;
|
|
187
192
|
logger.debug('diff-analysis-orchestrator - orchestrateBatches', 'Building file overview', {
|
|
188
193
|
fileCount: filesData.length
|
|
189
194
|
});
|
|
@@ -214,20 +219,20 @@ export const orchestrateBatches = async (filesData, { headless = false } = {}) =
|
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
logger.debug('diff-analysis-orchestrator - orchestrateBatches', 'Calling Opus orchestrator', {
|
|
217
|
-
model:
|
|
218
|
-
timeout:
|
|
222
|
+
model: orchModel,
|
|
223
|
+
timeout: orchTimeout
|
|
219
224
|
});
|
|
220
225
|
|
|
221
226
|
let rawResponse;
|
|
222
227
|
try {
|
|
223
228
|
rawResponse = await executeClaudeWithRetry(prompt, {
|
|
224
|
-
model:
|
|
225
|
-
timeout:
|
|
229
|
+
model: orchModel,
|
|
230
|
+
timeout: orchTimeout,
|
|
226
231
|
headless,
|
|
227
232
|
telemetryContext: {
|
|
228
233
|
hook: 'orchestrator',
|
|
229
234
|
fileCount: filesData.length,
|
|
230
|
-
model:
|
|
235
|
+
model: orchModel
|
|
231
236
|
}
|
|
232
237
|
});
|
|
233
238
|
} catch (err) {
|
package/lib/utils/judge.js
CHANGED
|
@@ -26,9 +26,9 @@ import { formatBlockingIssues, getAffectedFiles, formatFileContents } from './re
|
|
|
26
26
|
import { getRepoRoot, getRepoName, getCurrentBranch } from './git-operations.js';
|
|
27
27
|
import logger from './logger.js';
|
|
28
28
|
import { recordMetric } from './metrics.js';
|
|
29
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
29
30
|
|
|
30
|
-
const JUDGE_DEFAULT_MODEL = '
|
|
31
|
-
const JUDGE_TIMEOUT = 360000;
|
|
31
|
+
const { model: JUDGE_DEFAULT_MODEL, timeout: JUDGE_TIMEOUT } = getDefaultSection('judge');
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Applies a single search/replace fix to a file and stages it
|
|
@@ -103,7 +103,10 @@ const applyFix = async (fix, repoRoot) => {
|
|
|
103
103
|
* @returns {Promise<{fixedCount: number, falsePositiveCount: number, remainingIssues: Array, verdicts: Array}>}
|
|
104
104
|
*/
|
|
105
105
|
const judgeAndFix = async (analysisResult, filesData, config, { headless = false } = {}) => {
|
|
106
|
-
|
|
106
|
+
// Resolve judge config: remote settings.json > local defaults.json
|
|
107
|
+
const judgeConfig = await resolveSection('judge');
|
|
108
|
+
const model = config.judge?.model ?? judgeConfig?.model ?? JUDGE_DEFAULT_MODEL;
|
|
109
|
+
const judgeTimeout = judgeConfig?.timeout ?? JUDGE_TIMEOUT;
|
|
107
110
|
const repoRoot = getRepoRoot();
|
|
108
111
|
|
|
109
112
|
// Get all issues: prefer details (all severities), fallback to blockingIssues
|
|
@@ -135,7 +138,7 @@ const judgeAndFix = async (analysisResult, filesData, config, { headless = false
|
|
|
135
138
|
// Call LLM
|
|
136
139
|
const response = await executeClaudeWithRetry(prompt, {
|
|
137
140
|
model,
|
|
138
|
-
timeout:
|
|
141
|
+
timeout: judgeTimeout,
|
|
139
142
|
headless
|
|
140
143
|
});
|
|
141
144
|
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
import https from 'https';
|
|
20
20
|
import logger from './logger.js';
|
|
21
21
|
import { loadToken } from './token-store.js';
|
|
22
|
+
import { getDefaultSection } from './config-registry.js';
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
24
|
+
const linearDefaults = getDefaultSection('linear') || {};
|
|
25
|
+
const LINEAR_HOSTNAME = linearDefaults.hostname || 'api.linear.app';
|
|
26
|
+
const LINEAR_PATH = linearDefaults.path || '/graphql';
|
|
27
|
+
const LINEAR_TIMEOUT = linearDefaults.timeout || 10000;
|
|
28
|
+
const MAX_RETRIES = linearDefaults.maxRetries || 2;
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Custom error for Linear connector failures
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { getRepoRoot } from './git-operations.js';
|
|
28
28
|
import { which } from './which-command.js';
|
|
29
29
|
import { fetchRemoteConfig } from './remote-config.js';
|
|
30
|
+
import { resolveSection } from './config-registry.js';
|
|
30
31
|
import logger from './logger.js';
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -422,6 +423,9 @@ export async function runLinters(files, config, presetName = 'default') {
|
|
|
422
423
|
const timeout = lintConfig.timeout || 30000;
|
|
423
424
|
|
|
424
425
|
const tools = await getLinterToolsForPreset(presetName);
|
|
426
|
+
|
|
427
|
+
// Merge remote tools.json overrides (timeout, perFile, enabled) into tool definitions
|
|
428
|
+
const remoteToolsConfig = await resolveSection('tools');
|
|
425
429
|
const results = [];
|
|
426
430
|
|
|
427
431
|
logger.debug('linter-runner - runLinters', 'Starting linters', {
|
|
@@ -431,7 +435,18 @@ export async function runLinters(files, config, presetName = 'default') {
|
|
|
431
435
|
autoFix
|
|
432
436
|
});
|
|
433
437
|
|
|
434
|
-
for (
|
|
438
|
+
for (let toolDef of tools) {
|
|
439
|
+
// Apply remote tool config overrides (remote > local)
|
|
440
|
+
const remoteToolOverride = remoteToolsConfig?.[toolDef.name];
|
|
441
|
+
if (remoteToolOverride) {
|
|
442
|
+
toolDef = { ...toolDef };
|
|
443
|
+
if (remoteToolOverride.timeout !== undefined) toolDef.timeout = remoteToolOverride.timeout;
|
|
444
|
+
if (remoteToolOverride.perFile !== undefined) toolDef.perFile = remoteToolOverride.perFile;
|
|
445
|
+
if (remoteToolOverride.enabled === false) {
|
|
446
|
+
logger.info(`Skipping ${toolDef.name} (disabled by remote config)`);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
435
450
|
// Filter files by tool's extensions
|
|
436
451
|
const matchingFiles = filterFilesByTool(files, toolDef);
|
|
437
452
|
|
|
@@ -32,6 +32,7 @@ import { executeClaudeWithRetry, extractJSON } from './claude-client.js';
|
|
|
32
32
|
import { loadPrompt } from './prompt-builder.js';
|
|
33
33
|
import { getConfig } from '../config.js';
|
|
34
34
|
import logger from './logger.js';
|
|
35
|
+
import { getDefaultSection, resolveSection } from './config-registry.js';
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* @typedef {Object} BranchContext
|
|
@@ -58,13 +59,11 @@ import logger from './logger.js';
|
|
|
58
59
|
*/
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Internal defaults
|
|
62
|
+
* Internal defaults loaded from lib/defaults.json
|
|
62
63
|
* Why: Convention over configuration - sensible defaults for 95% of cases
|
|
64
|
+
* Fallbacks: timeout=180000 (3 min), maxDiffSize=51200 (50KB)
|
|
63
65
|
*/
|
|
64
|
-
const DEFAULTS = {
|
|
65
|
-
timeout: 180000, // 3 minutes
|
|
66
|
-
maxDiffSize: 50000 // 50KB
|
|
67
|
-
};
|
|
66
|
+
const DEFAULTS = getDefaultSection('prMetadata') || { timeout: 180000, maxDiffSize: 50000 };
|
|
68
67
|
|
|
69
68
|
/**
|
|
70
69
|
* Builds diff payload with tiered reduction strategy
|
|
@@ -310,12 +309,14 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
310
309
|
|
|
311
310
|
logger.debug('pr-metadata-engine - getBranchContext', 'Commits retrieved');
|
|
312
311
|
|
|
313
|
-
// Build diff payload with tiered reduction
|
|
312
|
+
// Build diff payload with tiered reduction (remote settings.json > local defaults)
|
|
313
|
+
const resolvedPrMeta = await resolveSection('prMetadata');
|
|
314
|
+
const maxDiffSize = resolvedPrMeta?.maxDiffSize || DEFAULTS.maxDiffSize;
|
|
314
315
|
const {
|
|
315
316
|
payload: diff,
|
|
316
317
|
isTruncated,
|
|
317
318
|
truncationDetails
|
|
318
|
-
} = buildDiffPayload(baseBranch, 'HEAD', files,
|
|
319
|
+
} = buildDiffPayload(baseBranch, 'HEAD', files, maxDiffSize);
|
|
319
320
|
|
|
320
321
|
logger.debug('pr-metadata-engine - getBranchContext', 'Diff payload built', {
|
|
321
322
|
diffSize: diff.length,
|
|
@@ -371,7 +372,9 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
371
372
|
export const generatePRMetadata = async (context, options = {}) => {
|
|
372
373
|
const config = await getConfig();
|
|
373
374
|
const configTimeout = config.analysis?.timeout;
|
|
374
|
-
const
|
|
375
|
+
const resolvedPrMetaGen = await resolveSection('prMetadata');
|
|
376
|
+
const defaultTimeout = resolvedPrMetaGen?.timeout || DEFAULTS.timeout;
|
|
377
|
+
const { timeout = configTimeout || defaultTimeout, hook = 'pr-metadata', headless = false, costTracker = null } = options;
|
|
375
378
|
|
|
376
379
|
logger.debug('pr-metadata-engine - generatePRMetadata', 'Generating PR metadata', {
|
|
377
380
|
filesCount: context.filesCount,
|