-
Notifications
You must be signed in to change notification settings - Fork 1
fix(session-start): move config require inside try-catch to honor exit-0 contract #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d7d31e6
20667c7
6dbf8ad
eaa92d2
44ba88f
a1c26ba
e4dcd8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Default configuration constants for hallucination-detector hooks. | ||
| * Zero dependencies — Node.js built-ins only. | ||
| */ | ||
|
|
||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Default weights for each detection category. | ||
| * Weights are relative severity signals; `aggregateWeightedScore` normalizes | ||
| * by their sum so aggregate scores always remain in [0, 1] regardless of | ||
| * whether the weights themselves sum to 1.0. | ||
| */ | ||
| const DEFAULT_WEIGHTS = { | ||
| speculation_language: 0.25, | ||
| causality_language: 0.3, | ||
| pseudo_quantification: 0.15, | ||
| completeness_claim: 0.2, | ||
| // fabricated_source: reserved for future implementation (issue #18) | ||
| evaluative_design_claim: 0.4, | ||
| internal_contradiction: 0.35, | ||
| unsupported_absence: 0.7, | ||
| ungrounded_behavioral_assertion: 0.5, | ||
| }; | ||
|
|
||
| /** | ||
| * Default score thresholds for three-tier label classification. | ||
| * - uncertain: scores >= this value are labelled UNCERTAIN (not GROUNDED) | ||
| * - hallucinated: scores > this value are labelled HALLUCINATED | ||
| */ | ||
| const DEFAULT_THRESHOLDS = { | ||
| uncertain: 0.3, | ||
| hallucinated: 0.6, | ||
| }; | ||
|
|
||
| /** | ||
| * Default weights for the four confidence-score components. | ||
| * These control how much each factor contributes to the per-match | ||
| * confidence integer in [0, 100]. | ||
| * | ||
| * - patternStrength: contribution of the pattern's inherent severity | ||
| * - evidenceProximity: contribution of evidence markers near the match | ||
| * - categoryStacking: bonus when multiple categories fire in the same sentence | ||
| * - contextDensity: bonus when multiple matches cluster within 200 chars | ||
| */ | ||
| const DEFAULT_CONFIDENCE_WEIGHTS = { | ||
| patternStrength: 0.4, | ||
| evidenceProximity: 0.25, | ||
| categoryStacking: 0.2, | ||
| contextDensity: 0.15, | ||
| }; | ||
|
|
||
| /** | ||
| * Default full configuration object. | ||
| */ | ||
| const DEFAULT_CONFIG = { | ||
| weights: DEFAULT_WEIGHTS, | ||
| thresholds: DEFAULT_THRESHOLDS, | ||
| introspect: false, | ||
| introspectOutputPath: null, | ||
| // Shadow mode: log would-block events without actually blocking. | ||
| dryRun: false, | ||
| // Global settings | ||
| severity: 'error', | ||
| maxTriggersPerResponse: 20, | ||
| maxBlocksPerSession: null, | ||
| outputFormat: 'text', | ||
| debug: false, | ||
| // Per-category settings (keyed by category name) | ||
| categories: {}, | ||
| // Filtering settings | ||
| ignorePatterns: [], | ||
| ignoreBlocks: [], | ||
| evidenceMarkers: [], | ||
| allowlist: [], | ||
| // Response settings | ||
| responseTemplates: {}, | ||
| includeContext: true, | ||
| contextLines: 2, | ||
| // Session-type gating | ||
| warnOnly: false, // log telemetry but never emit a block to stdout | ||
| ignoreCategories: [], // category names skipped entirely (still written to telemetry with was_ignored=1) | ||
| blockSubagents: false, // block when hook_event_name is SubagentStop | ||
| blockUserSessions: true, // block when hook_event_name is Stop (user-facing session) | ||
| // Confidence scoring | ||
| confidenceWeights: DEFAULT_CONFIDENCE_WEIGHTS, | ||
| reportingThreshold: 50, // minimum confidence [0,100] for a match to appear in block reason text | ||
| }; | ||
|
|
||
| module.exports = { | ||
| DEFAULT_WEIGHTS, | ||
| DEFAULT_THRESHOLDS, | ||
| DEFAULT_CONFIDENCE_WEIGHTS, | ||
| DEFAULT_CONFIG, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Deep freeze and deep merge utilities for hallucination-detector config objects. | ||
| * Zero dependencies — Node.js built-ins only. | ||
| */ | ||
|
|
||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Recursively freeze an object and all nested plain objects / arrays. | ||
| * @param {*} obj | ||
| * @returns {*} The frozen value. | ||
| */ | ||
| function deepFreeze(obj) { | ||
| if (obj === null || typeof obj !== 'object') return obj; | ||
| for (const val of Object.values(obj)) { | ||
| deepFreeze(val); | ||
| } | ||
| return Object.freeze(obj); | ||
| } | ||
|
|
||
| /** | ||
| * Deep-merge two config objects. Rules: | ||
| * - Plain objects are merged recursively. | ||
| * - `categories.<name>.customPatterns` arrays are concatenated unless the override | ||
| * has `replacePatterns: true` for that category. | ||
| * - All other arrays are replaced by the override value. | ||
| * - Scalar values are replaced by the override value. | ||
| * | ||
| * Neither argument is mutated; a new object is returned. | ||
| * | ||
| * @param {object} base - Lower-priority config. | ||
| * @param {object} override - Higher-priority config (wins on conflict). | ||
| * @returns {object} Merged config. | ||
| */ | ||
| function mergeConfig(base, override) { | ||
| if (!override || typeof override !== 'object' || Array.isArray(override)) return base; | ||
| if (!base || typeof base !== 'object' || Array.isArray(base)) return override; | ||
|
|
||
| const result = { ...base }; | ||
|
|
||
| for (const key of Object.keys(override)) { | ||
| if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue; | ||
| const overVal = override[key]; | ||
| const baseVal = base[key]; | ||
|
|
||
| if (key === 'categories') { | ||
| // Merge categories map, with special customPatterns concatenation logic. | ||
| const baseCats = | ||
| typeof baseVal === 'object' && baseVal !== null && !Array.isArray(baseVal) ? baseVal : {}; | ||
| const overCats = | ||
| typeof overVal === 'object' && overVal !== null && !Array.isArray(overVal) ? overVal : {}; | ||
| const merged = { ...baseCats }; | ||
| for (const catName of Object.keys(overCats)) { | ||
| if (catName === '__proto__' || catName === 'constructor' || catName === 'prototype') | ||
| continue; | ||
| const baseCat = baseCats[catName] || {}; | ||
| const overCat = overCats[catName]; | ||
| if (typeof overCat !== 'object' || overCat === null) { | ||
| merged[catName] = overCat; | ||
| continue; | ||
| } | ||
| // Extract customPatterns and replacePatterns before spreading to handle | ||
| // replacePatterns:true correctly even when overCat.customPatterns is absent. | ||
| const { customPatterns: basePatterns, ...baseCatRest } = baseCat; | ||
| const { customPatterns: overPatterns, replacePatterns, ...overCatRest } = overCat; | ||
| const mergedCat = { ...baseCatRest, ...overCatRest }; | ||
|
Comment on lines
+59
to
+67
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle array category overrides as non-mergeable values. At Line 59, Suggested patch- if (typeof overCat !== 'object' || overCat === null) {
+ if (typeof overCat !== 'object' || overCat === null || Array.isArray(overCat)) {
merged[catName] = overCat;
continue;
}
+ const baseCatObj =
+ typeof baseCat === 'object' && baseCat !== null && !Array.isArray(baseCat)
+ ? baseCat
+ : {};
// Extract customPatterns and replacePatterns before spreading to handle
// replacePatterns:true correctly even when overCat.customPatterns is absent.
- const { customPatterns: basePatterns, ...baseCatRest } = baseCat;
+ const { customPatterns: basePatterns, ...baseCatRest } = baseCatObj;🤖 Prompt for AI Agents |
||
| if (replacePatterns) { | ||
| mergedCat.customPatterns = overPatterns !== undefined ? overPatterns : []; | ||
| mergedCat.replacePatterns = true; | ||
| } else if (Array.isArray(basePatterns) && Array.isArray(overPatterns)) { | ||
| mergedCat.customPatterns = [...basePatterns, ...overPatterns]; | ||
| } else if (Array.isArray(basePatterns)) { | ||
| mergedCat.customPatterns = basePatterns; | ||
| } else if (Array.isArray(overPatterns)) { | ||
| mergedCat.customPatterns = overPatterns; | ||
| } | ||
| merged[catName] = mergedCat; | ||
| } | ||
| result[key] = merged; | ||
| } else if ( | ||
| typeof overVal === 'object' && | ||
| overVal !== null && | ||
| !Array.isArray(overVal) && | ||
| typeof baseVal === 'object' && | ||
| baseVal !== null && | ||
| !Array.isArray(baseVal) | ||
| ) { | ||
| // Both are plain objects — recurse. | ||
| result[key] = mergeConfig(baseVal, overVal); | ||
| } else { | ||
| // Scalar, array, or null — override wins. | ||
| result[key] = overVal; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| module.exports = { mergeConfig, deepFreeze }; | ||
Uh oh!
There was an error while loading. Please reload this page.