mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-07 10:17:53 +02:00
feat(session-start): make instinct injection count and confidence threshold configurable (#2413)
* feat(session-start): make instinct injection count and confidence threshold configurable Expose ECC_MAX_INJECTED_INSTINCTS and ECC_INSTINCT_CONFIDENCE_THRESHOLD so operators can tune SessionStart instinct injection without editing source. Defaults are unchanged (6 instincts, 0.7 confidence floor). The two previously hardcoded constants become DEFAULT_-prefixed fallbacks, resolved through getMaxInjectedInstincts() and getInstinctConfidenceThreshold(), mirroring the existing getSessionRetentionDays() / getSessionStartMaxContextChars() env-override pattern already in this file. Invalid or out-of-range values fall back to the defaults. Adds subprocess coverage in tests/hooks/hooks.test.js and documents both variables in the README Hook Runtime Controls section. Implements part (a) of #2371. * fix(session-start): reject partial env values for instinct injection knobs Parse ECC_MAX_INJECTED_INSTINCTS and ECC_INSTINCT_CONFIDENCE_THRESHOLD with Number() (after trim) instead of parseInt/parseFloat, so malformed values like "3.9", "6abc", or "0.7x" fall back to the default rather than silently accepting the numeric prefix (parseInt("3.9")=3, parseFloat("0.7x")=0.7). Adds a regression assertion that a non-integer count falls back to 6. * fix(session-start): validate decimal grammar for instinct injection env vars Number() still accepts non-decimal numeric syntax, so ECC_INSTINCT_CONFIDENCE_THRESHOLD=0x1 resolved to 1 and ECC_MAX_INJECTED_INSTINCTS=1e2 to 100. Gate each value on a strict format (/^\d+(\.\d+)?$/ for the 0-1 threshold, /^\d+$/ for the positive-integer count) before converting, so hex/exponent/partial values fall back to the default. Adds regression assertions for 1e2 and 0x1.
This commit is contained in:
@@ -27,8 +27,8 @@ const { detectProjectType } = require('../lib/project-detect');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const INSTINCT_CONFIDENCE_THRESHOLD = 0.7;
|
||||
const MAX_INJECTED_INSTINCTS = 6;
|
||||
const DEFAULT_INSTINCT_CONFIDENCE_THRESHOLD = 0.7;
|
||||
const DEFAULT_MAX_INJECTED_INSTINCTS = 6;
|
||||
const MAX_INJECTED_LEARNED_SKILLS = 6;
|
||||
const MAX_LEARNED_SKILL_SUMMARY_CHARS = 220;
|
||||
const DEFAULT_SESSION_START_CONTEXT_MAX_CHARS = 8000;
|
||||
@@ -116,6 +116,52 @@ function getSessionStartMaxContextChars() {
|
||||
return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SESSION_START_CONTEXT_MAX_CHARS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the minimum confidence an instinct needs to be injected at
|
||||
* SessionStart. Overridable via `ECC_INSTINCT_CONFIDENCE_THRESHOLD`
|
||||
* (a number in [0, 1]); falsy or out-of-range values fall back to
|
||||
* {@link DEFAULT_INSTINCT_CONFIDENCE_THRESHOLD}.
|
||||
*
|
||||
* @returns {number} The confidence floor for injected instincts.
|
||||
*/
|
||||
function getInstinctConfidenceThreshold() {
|
||||
const raw = process.env.ECC_INSTINCT_CONFIDENCE_THRESHOLD;
|
||||
if (!raw) return DEFAULT_INSTINCT_CONFIDENCE_THRESHOLD;
|
||||
|
||||
// Require a plain decimal (e.g. "0.7", "1", "0.95") so trailing junk
|
||||
// ("0.7x") and non-decimal numeric syntax like "0x1" (hex) or "1e2"
|
||||
// (exponent) are rejected whole rather than silently accepted by Number().
|
||||
const normalized = raw.trim();
|
||||
if (!/^\d+(\.\d+)?$/.test(normalized)) return DEFAULT_INSTINCT_CONFIDENCE_THRESHOLD;
|
||||
|
||||
const parsed = Number(normalized);
|
||||
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1
|
||||
? parsed
|
||||
: DEFAULT_INSTINCT_CONFIDENCE_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the maximum number of instincts injected at SessionStart.
|
||||
* Overridable via `ECC_MAX_INJECTED_INSTINCTS` (a positive integer);
|
||||
* falsy or invalid values fall back to
|
||||
* {@link DEFAULT_MAX_INJECTED_INSTINCTS}.
|
||||
*
|
||||
* @returns {number} The cap on injected instincts.
|
||||
*/
|
||||
function getMaxInjectedInstincts() {
|
||||
const raw = process.env.ECC_MAX_INJECTED_INSTINCTS;
|
||||
if (!raw) return DEFAULT_MAX_INJECTED_INSTINCTS;
|
||||
|
||||
// Require a plain non-negative integer so "3.9", "6abc", "0x1" (hex),
|
||||
// and "1e2" (exponent) are rejected whole and fall back to the default,
|
||||
// rather than parseInt truncating or Number() accepting alternate syntax.
|
||||
const normalized = raw.trim();
|
||||
if (!/^\d+$/.test(normalized)) return DEFAULT_MAX_INJECTED_INSTINCTS;
|
||||
|
||||
const parsed = Number(normalized);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_INJECTED_INSTINCTS;
|
||||
}
|
||||
|
||||
function getSessionStartMode(rawInput) {
|
||||
const input = String(rawInput || '');
|
||||
if (!input.trim()) return null;
|
||||
@@ -373,9 +419,12 @@ function summarizeActiveInstincts(observerContext) {
|
||||
...globalDirs.flatMap(({ dir, scope }) => readInstinctsFromDir(dir, scope)),
|
||||
];
|
||||
|
||||
const confidenceThreshold = getInstinctConfidenceThreshold();
|
||||
const maxInjected = getMaxInjectedInstincts();
|
||||
|
||||
const deduped = new Map();
|
||||
for (const instinct of scopedInstincts) {
|
||||
if (!instinct.id || instinct.confidence < INSTINCT_CONFIDENCE_THRESHOLD) continue;
|
||||
if (!instinct.id || instinct.confidence < confidenceThreshold) continue;
|
||||
const existing = deduped.get(instinct.id);
|
||||
if (!existing || (existing._scopeLabel !== 'project' && instinct._scopeLabel === 'project')) {
|
||||
deduped.set(instinct.id, instinct);
|
||||
@@ -393,7 +442,7 @@ function summarizeActiveInstincts(observerContext) {
|
||||
if (left._scopeLabel !== right._scopeLabel) return left._scopeLabel === 'project' ? -1 : 1;
|
||||
return String(left.id).localeCompare(String(right.id));
|
||||
})
|
||||
.slice(0, MAX_INJECTED_INSTINCTS);
|
||||
.slice(0, maxInjected);
|
||||
|
||||
if (ranked.length === 0) {
|
||||
return '';
|
||||
|
||||
Reference in New Issue
Block a user