claude-git-hooks 2.43.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 +49 -0
- package/README.md +2 -2
- package/lib/commands/analyze-pr.js +9 -17
- package/lib/commands/help.js +169 -80
- 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/templates/HELP_NAVIGATE.md +41 -0
- package/templates/HELP_QUERY.md +7 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,55 @@ 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
|
+
|
|
35
|
+
## [2.44.0] - 2026-05-04
|
|
36
|
+
|
|
37
|
+
### ✨ Added
|
|
38
|
+
- New `HELP_NAVIGATE.md` prompt template for the first-pass catalog navigation (SUE-152)
|
|
39
|
+
- Auto-discovery of `.library/` catalog files (by-code, by-domain, by-task-type shelves) for AI help context
|
|
40
|
+
|
|
41
|
+
### 🔧 Changed
|
|
42
|
+
- Rewrote AI help command to use a two-pass librarian approach navigating local `.library/` instead of single-pass CLAUDE.md lookup (SUE-152)
|
|
43
|
+
- Pass 1 sends project catalog to Claude; if deeper detail is needed, Pass 2 reads specific book files from local disk — no GitHub API calls
|
|
44
|
+
- Updated `HELP_QUERY.md` template to accept catalog + book content instead of flat documentation
|
|
45
|
+
- Replaced `NEED_MORE_CONTEXT: file1, file2` protocol with structured `ANSWER:` / `BOOKS:` response format
|
|
46
|
+
- Updated `report-issue` flow to use internal `_readPackageFile()` helper instead of the removed `readClaudeMd()`
|
|
47
|
+
- Updated unit tests to cover two-pass librarian flow, catalog mocking, and book-read scenarios
|
|
48
|
+
|
|
49
|
+
### 🔒 Security
|
|
50
|
+
- Added path traversal protection when reading LLM-requested book paths — resolved paths are verified against the package root before disk access
|
|
51
|
+
|
|
52
|
+
### 🗑️ Removed
|
|
53
|
+
- Removed GitHub API dependency (`fetchFileContent`, `parseGitHubRepo`) from AI help flow — all context is now read from local disk
|
|
54
|
+
- Removed `readClaudeMd()` single-file reader in favor of `readLibraryCatalog()` multi-file catalog builder
|
|
55
|
+
|
|
56
|
+
|
|
8
57
|
## [2.43.0] - 2026-04-30
|
|
9
58
|
|
|
10
59
|
### ✨ Added
|
package/README.md
CHANGED
|
@@ -295,7 +295,7 @@ claude-hooks telemetry clear # Clear data
|
|
|
295
295
|
### Help & Issue Reporting
|
|
296
296
|
|
|
297
297
|
```bash
|
|
298
|
-
claude-hooks help "how do presets work?" # AI-powered help (
|
|
298
|
+
claude-hooks help "how do presets work?" # AI-powered help (navigates .library/)
|
|
299
299
|
claude-hooks help --report-issue # Interactive GitHub issue creation
|
|
300
300
|
claude-hooks --help # Static command reference
|
|
301
301
|
```
|
|
@@ -355,7 +355,7 @@ claude-hooks update # Update to latest version
|
|
|
355
355
|
| `debug.js` | **Debug toggle** - enable/disable verbose logging | `runSetDebug()` |
|
|
356
356
|
| `telemetry-cmd.js` | **Telemetry commands** - show/clear statistics | `runShowTelemetry()`, `runClearTelemetry()` |
|
|
357
357
|
| `diff-batch-info.js` | **Batch info** - orchestration config + per-model speed telemetry | `runDiffBatchInfo()` |
|
|
358
|
-
| `help.js` | **Help, AI
|
|
358
|
+
| `help.js` | **Help, AI librarian, report-issue** | `runShowHelp()`, `showStaticHelp()`, `runShowVersion()` |
|
|
359
359
|
|
|
360
360
|
### Utility Modules (`lib/utils/`)
|
|
361
361
|
|
|
@@ -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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Features:
|
|
6
6
|
* - Dynamic help from command registry (no args, --help, -h)
|
|
7
|
-
* - AI help:
|
|
7
|
+
* - AI help: two-pass librarian navigating .library/ for answers
|
|
8
8
|
* - Report issue: interactive issue creation guided by Claude
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -160,59 +160,137 @@ function printAiResponse(response, source) {
|
|
|
160
160
|
console.log('');
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/** Package root directory — resolved once from __dirname */
|
|
164
|
+
const _packageRoot = path.join(__dirname, '..', '..');
|
|
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
|
+
|
|
163
173
|
/**
|
|
164
|
-
*
|
|
165
|
-
* Why:
|
|
174
|
+
* Catalog directories within .library/ to auto-discover
|
|
175
|
+
* Why: These contain index and shelf files that form the navigational catalog.
|
|
176
|
+
* books/ is excluded — those are fetched on-demand in Pass 2.
|
|
177
|
+
*/
|
|
178
|
+
const _CATALOG_DIRS = ['by-code', 'by-domain', 'by-task-type'];
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Read a single file from package root, returning null on failure
|
|
166
182
|
*
|
|
167
|
-
* @
|
|
183
|
+
* @param {string} relativePath - Path relative to package root
|
|
184
|
+
* @returns {Promise<string|null>} File content or null
|
|
168
185
|
*/
|
|
169
|
-
const
|
|
170
|
-
const claudeMdPath = path.join(__dirname, '..', '..', 'CLAUDE.md');
|
|
186
|
+
const _readPackageFile = async (relativePath) => {
|
|
171
187
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
return await fs.readFile(path.join(_packageRoot, relativePath), 'utf8');
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Read the project catalog from .library/ and CLAUDE.md
|
|
196
|
+
* Why: The catalog provides navigational context for the AI librarian (Pass 1).
|
|
197
|
+
* Auto-discovers index/shelf files so the catalog stays current as .library/ grows.
|
|
198
|
+
*
|
|
199
|
+
* Reads:
|
|
200
|
+
* - CLAUDE.md (global rules)
|
|
201
|
+
* - .library/index.md (repo identity, capabilities)
|
|
202
|
+
* - .library/conventions.md (coding standards)
|
|
203
|
+
* - .library/by-code/*.md (source-path shelves)
|
|
204
|
+
* - .library/by-domain/*.md (workflow reading lists)
|
|
205
|
+
* - .library/by-task-type/*.md (task guidance)
|
|
206
|
+
*
|
|
207
|
+
* @returns {Promise<string|null>} Concatenated catalog or null if nothing could be read
|
|
208
|
+
*/
|
|
209
|
+
const readLibraryCatalog = async () => {
|
|
210
|
+
const sections = [];
|
|
211
|
+
|
|
212
|
+
// Fixed files: CLAUDE.md + core library files
|
|
213
|
+
const fixedFiles = [
|
|
214
|
+
{ label: 'CLAUDE.md', path: 'CLAUDE.md' },
|
|
215
|
+
{ label: '.library/index.md', path: '.library/index.md' },
|
|
216
|
+
{ label: '.library/conventions.md', path: '.library/conventions.md' }
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
for (const file of fixedFiles) {
|
|
220
|
+
const content = await _readPackageFile(file.path);
|
|
221
|
+
if (content) {
|
|
222
|
+
sections.push(`--- ${file.label} ---\n${content}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Auto-discover .md files in each catalog directory
|
|
227
|
+
for (const dir of _CATALOG_DIRS) {
|
|
228
|
+
const dirPath = path.join(_packageRoot, '.library', dir);
|
|
229
|
+
let entries;
|
|
230
|
+
try {
|
|
231
|
+
entries = await fs.readdir(dirPath);
|
|
232
|
+
} catch {
|
|
233
|
+
logger.debug('help - readLibraryCatalog', `Could not read .library/${dir}/`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const mdFiles = entries.filter((f) => f.endsWith('.md')).sort();
|
|
238
|
+
for (const file of mdFiles) {
|
|
239
|
+
const relativePath = `.library/${dir}/${file}`;
|
|
240
|
+
const content = await _readPackageFile(relativePath);
|
|
241
|
+
if (content) {
|
|
242
|
+
sections.push(`--- ${relativePath} ---\n${content}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (sections.length === 0) {
|
|
248
|
+
logger.debug('help - readLibraryCatalog', 'No catalog files found');
|
|
180
249
|
return null;
|
|
181
250
|
}
|
|
251
|
+
|
|
252
|
+
const catalog = sections.join('\n\n');
|
|
253
|
+
logger.debug('help - readLibraryCatalog', 'Catalog loaded', {
|
|
254
|
+
sections: sections.length,
|
|
255
|
+
length: catalog.length
|
|
256
|
+
});
|
|
257
|
+
return catalog;
|
|
182
258
|
};
|
|
183
259
|
|
|
184
260
|
/**
|
|
185
|
-
* AI-powered help:
|
|
186
|
-
* Why: Provides intelligent answers
|
|
261
|
+
* AI-powered help: two-pass librarian navigating .library/
|
|
262
|
+
* Why: Provides intelligent answers by reading the project catalog, then
|
|
263
|
+
* fetching specific books from local disk only when deeper detail is needed.
|
|
187
264
|
*
|
|
188
265
|
* Flow:
|
|
189
|
-
* 1. Read
|
|
190
|
-
* 2.
|
|
191
|
-
* 3.
|
|
192
|
-
* 4. If
|
|
193
|
-
* 5.
|
|
194
|
-
* 6. On any error: fall back to static help
|
|
266
|
+
* 1. Read catalog from .library/ (auto-discovered indexes + shelves)
|
|
267
|
+
* 2. Pass 1: Send catalog to Claude via HELP_NAVIGATE.md
|
|
268
|
+
* 3. If ANSWER: display directly (1 Claude call)
|
|
269
|
+
* 4. If BOOKS: read requested books from local disk, send to HELP_QUERY.md (2 Claude calls)
|
|
270
|
+
* 5. On any error: fall back to static help
|
|
195
271
|
*
|
|
196
272
|
* @param {string} question - User's natural language question
|
|
197
273
|
*/
|
|
198
274
|
async function runAiHelp(question) {
|
|
199
275
|
try {
|
|
200
|
-
const
|
|
201
|
-
if (!
|
|
276
|
+
const catalog = await readLibraryCatalog();
|
|
277
|
+
if (!catalog) {
|
|
202
278
|
logger.debug(
|
|
203
279
|
'help - runAiHelp',
|
|
204
|
-
'
|
|
280
|
+
'Catalog not available, falling back to static help'
|
|
205
281
|
);
|
|
206
282
|
showStaticHelp();
|
|
207
283
|
return;
|
|
208
284
|
}
|
|
209
285
|
|
|
210
|
-
const prompt = await loadPrompt('
|
|
211
|
-
|
|
286
|
+
const prompt = await loadPrompt('HELP_NAVIGATE.md', {
|
|
287
|
+
CATALOG: catalog,
|
|
212
288
|
QUESTION: question
|
|
213
289
|
});
|
|
214
290
|
|
|
215
|
-
logger.debug('help - runAiHelp', 'Querying Claude', {
|
|
291
|
+
logger.debug('help - runAiHelp', 'Querying Claude (Pass 1)', {
|
|
292
|
+
questionLength: question.length
|
|
293
|
+
});
|
|
216
294
|
console.log('\nSearching documentation...\n');
|
|
217
295
|
|
|
218
296
|
const pkg = getPackageJson();
|
|
@@ -221,95 +299,106 @@ async function runAiHelp(question) {
|
|
|
221
299
|
const response = await executeClaudeWithRetry(prompt, { timeout: 60000 });
|
|
222
300
|
const trimmedResponse = response.trim();
|
|
223
301
|
|
|
224
|
-
// Check for
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const enrichedResponse = await handleNeedMoreContext(
|
|
230
|
-
needMoreLine,
|
|
302
|
+
// Check for BOOKS: second pass
|
|
303
|
+
const booksLine = trimmedResponse.split('\n').find((l) => l.startsWith('BOOKS:'));
|
|
304
|
+
if (booksLine) {
|
|
305
|
+
const enrichedAnswer = await readAndAnswerWithBooks(
|
|
306
|
+
booksLine,
|
|
231
307
|
question,
|
|
232
|
-
|
|
308
|
+
catalog
|
|
233
309
|
);
|
|
234
|
-
if (
|
|
235
|
-
printAiResponse(
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
// Enrichment failed: show first-pass answer with marker line stripped
|
|
239
|
-
const cleanResponse = trimmedResponse
|
|
240
|
-
.split('\n')
|
|
241
|
-
.filter((l) => !l.includes('NEED_MORE_CONTEXT'))
|
|
242
|
-
.join('\n')
|
|
243
|
-
.trim();
|
|
244
|
-
if (cleanResponse) {
|
|
245
|
-
printAiResponse(cleanResponse, localVersion);
|
|
310
|
+
if (enrichedAnswer) {
|
|
311
|
+
printAiResponse(enrichedAnswer, `${localVersion} + source`);
|
|
246
312
|
return;
|
|
247
313
|
}
|
|
314
|
+
// Books not readable — avoid surfacing raw BOOKS: line to the user
|
|
315
|
+
showStaticHelp();
|
|
316
|
+
return;
|
|
248
317
|
}
|
|
249
318
|
|
|
250
|
-
|
|
251
|
-
|
|
319
|
+
// ANSWER: response or fallback — strip the "ANSWER:" prefix if present
|
|
320
|
+
const answerLine = trimmedResponse.indexOf('ANSWER:');
|
|
321
|
+
const answer =
|
|
322
|
+
answerLine !== -1
|
|
323
|
+
? trimmedResponse.substring(answerLine + 'ANSWER:'.length).trim()
|
|
324
|
+
: trimmedResponse;
|
|
325
|
+
|
|
326
|
+
printAiResponse(answer, localVersion);
|
|
327
|
+
} catch (err) {
|
|
252
328
|
logger.debug('help - runAiHelp', 'AI help failed, falling back to static help', {
|
|
253
|
-
error:
|
|
329
|
+
error: err.message
|
|
254
330
|
});
|
|
255
331
|
showStaticHelp();
|
|
256
332
|
}
|
|
257
333
|
}
|
|
258
334
|
|
|
259
335
|
/**
|
|
260
|
-
*
|
|
336
|
+
* Pass 2: Read requested books from local disk and answer with enriched context
|
|
261
337
|
*
|
|
262
|
-
* @param {string}
|
|
338
|
+
* @param {string} booksLine - The line starting with "BOOKS:" containing comma-separated paths
|
|
263
339
|
* @param {string} question - Original user question
|
|
264
|
-
* @param {string}
|
|
265
|
-
* @returns {Promise<string|null>} Enriched
|
|
340
|
+
* @param {string} catalog - The catalog content from Pass 1
|
|
341
|
+
* @returns {Promise<string|null>} Enriched answer or null on failure
|
|
266
342
|
*/
|
|
267
|
-
async function
|
|
343
|
+
async function readAndAnswerWithBooks(booksLine, question, catalog) {
|
|
268
344
|
try {
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
const filePaths = pathsPart
|
|
345
|
+
const pathsPart = booksLine.replace('BOOKS:', '').trim();
|
|
346
|
+
const bookPaths = pathsPart
|
|
272
347
|
.split(',')
|
|
273
348
|
.map((p) => p.trim())
|
|
274
349
|
.filter(Boolean);
|
|
275
350
|
|
|
276
|
-
if (
|
|
277
|
-
logger.debug('help -
|
|
351
|
+
if (bookPaths.length === 0) {
|
|
352
|
+
logger.debug('help - readAndAnswerWithBooks', 'No book paths in BOOKS line');
|
|
278
353
|
return null;
|
|
279
354
|
}
|
|
280
355
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
}
|
|
284
363
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
364
|
+
logger.debug('help - readAndAnswerWithBooks', 'Reading books from disk', { bookPaths });
|
|
365
|
+
|
|
366
|
+
// Read books from local filesystem in parallel
|
|
367
|
+
// Contain LLM-sourced paths to the package root to prevent traversal
|
|
368
|
+
const readResults = await Promise.all(
|
|
369
|
+
bookPaths.map(async (bookPath) => {
|
|
370
|
+
const resolvedPath = path.resolve(_packageRoot, bookPath);
|
|
371
|
+
if (!resolvedPath.startsWith(_packageRoot + path.sep)) {
|
|
372
|
+
logger.debug('help - readAndAnswerWithBooks', 'Path traversal blocked', { bookPath });
|
|
373
|
+
return { bookPath, content: null };
|
|
374
|
+
}
|
|
375
|
+
const content = await _readPackageFile(bookPath);
|
|
376
|
+
return { bookPath, content };
|
|
290
377
|
})
|
|
291
378
|
);
|
|
292
379
|
|
|
293
|
-
|
|
294
|
-
const additionalContent = fetchResults
|
|
380
|
+
const bookContent = readResults
|
|
295
381
|
.filter((r) => r.content !== null)
|
|
296
|
-
.map((r) =>
|
|
297
|
-
.join('\n');
|
|
382
|
+
.map((r) => `--- ${r.bookPath} ---\n${r.content}`)
|
|
383
|
+
.join('\n\n');
|
|
298
384
|
|
|
299
|
-
if (!
|
|
300
|
-
logger.debug('help -
|
|
385
|
+
if (!bookContent) {
|
|
386
|
+
logger.debug('help - readAndAnswerWithBooks', 'No book content read from disk');
|
|
301
387
|
return null;
|
|
302
388
|
}
|
|
303
389
|
|
|
304
390
|
const enrichedPrompt = await loadPrompt('HELP_QUERY.md', {
|
|
305
|
-
|
|
391
|
+
CATALOG: catalog,
|
|
392
|
+
BOOKS: bookContent,
|
|
306
393
|
QUESTION: question
|
|
307
394
|
});
|
|
308
395
|
|
|
309
396
|
const enrichedResponse = await executeClaudeWithRetry(enrichedPrompt, { timeout: 60000 });
|
|
310
397
|
return enrichedResponse.trim();
|
|
311
|
-
} catch (
|
|
312
|
-
logger.debug('help -
|
|
398
|
+
} catch (err) {
|
|
399
|
+
logger.debug('help - readAndAnswerWithBooks', 'Book enrichment failed', {
|
|
400
|
+
error: err.message
|
|
401
|
+
});
|
|
313
402
|
return null;
|
|
314
403
|
}
|
|
315
404
|
}
|
|
@@ -371,8 +460,8 @@ async function runReportIssue() {
|
|
|
371
460
|
return;
|
|
372
461
|
}
|
|
373
462
|
|
|
374
|
-
// Step 5: Read CLAUDE.md for project context
|
|
375
|
-
const claudeMdContent = await
|
|
463
|
+
// Step 5: Read CLAUDE.md for project context (report-issue only needs basic context)
|
|
464
|
+
const claudeMdContent = await _readPackageFile('CLAUDE.md');
|
|
376
465
|
|
|
377
466
|
// Step 6: Generate questions from template using Claude
|
|
378
467
|
const questionsPrompt = await loadPrompt('HELP_REPORT_ISSUE.md', {
|
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
|
|