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:
Gaurav Dubey 2026-07-04 09:10:17 +05:30 committed by GitHub
parent 41599069c3
commit ff4a06dd91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 148 additions and 4 deletions

View file

@ -511,6 +511,95 @@ async function runTests() {
passed++;
else failed++;
if (
await asyncTest('honors ECC_MAX_INJECTED_INSTINCTS for injected instincts', async () => {
const isoHome = path.join(os.tmpdir(), `ecc-max-instincts-${Date.now()}`);
const homunculusDir = path.join(isoHome, 'homunculus');
const instinctsDir = path.join(homunculusDir, 'instincts', 'personal');
fs.mkdirSync(instinctsDir, { recursive: true });
for (let i = 1; i <= 8; i++) {
fs.writeFileSync(
path.join(instinctsDir, `instinct-${i}.md`),
`---\nid: max-instinct-${i}\nconfidence: 0.9\n---\n## Action\nDo configurable thing number ${i}.\n`
);
}
const baseEnv = { HOME: isoHome, USERPROFILE: isoHome, CLV2_HOMUNCULUS_DIR: homunculusDir };
try {
const def = await runScript(path.join(scriptsDir, 'session-start.js'), '', baseEnv);
assert.strictEqual(def.code, 0);
assert.ok(def.stderr.includes('Injecting 6 instinct(s)'), `default cap should inject 6, stderr: ${def.stderr}`);
const capped = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_MAX_INJECTED_INSTINCTS: '3' });
assert.strictEqual(capped.code, 0);
assert.ok(capped.stderr.includes('Injecting 3 instinct(s)'), `override should inject 3, stderr: ${capped.stderr}`);
const garbage = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_MAX_INJECTED_INSTINCTS: 'not-a-number' });
assert.strictEqual(garbage.code, 0);
assert.ok(garbage.stderr.includes('Injecting 6 instinct(s)'), `garbage override should fall back to default 6, stderr: ${garbage.stderr}`);
// A partial/non-integer value must be rejected whole, not truncated.
const partial = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_MAX_INJECTED_INSTINCTS: '3.9' });
assert.strictEqual(partial.code, 0);
assert.ok(partial.stderr.includes('Injecting 6 instinct(s)'), `non-integer override (3.9) should fall back to default 6, not truncate to 3, stderr: ${partial.stderr}`);
// Non-decimal numeric syntax (exponent, hex) must be rejected too.
// If "1e2" were accepted as 100, all 8 fixtures would inject; the
// default cap of 6 proves it fell back.
const exponent = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_MAX_INJECTED_INSTINCTS: '1e2' });
assert.strictEqual(exponent.code, 0);
assert.ok(exponent.stderr.includes('Injecting 6 instinct(s)'), `exponent override (1e2) should fall back to default 6, stderr: ${exponent.stderr}`);
} finally {
fs.rmSync(isoHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
await asyncTest('honors ECC_INSTINCT_CONFIDENCE_THRESHOLD for injected instincts', async () => {
const isoHome = path.join(os.tmpdir(), `ecc-instinct-threshold-${Date.now()}`);
const homunculusDir = path.join(isoHome, 'homunculus');
const instinctsDir = path.join(homunculusDir, 'instincts', 'personal');
fs.mkdirSync(instinctsDir, { recursive: true });
// 4 high-confidence (0.9) + 4 low-confidence (0.6) instincts.
for (let i = 1; i <= 8; i++) {
const confidence = i <= 4 ? '0.9' : '0.6';
fs.writeFileSync(
path.join(instinctsDir, `instinct-${i}.md`),
`---\nid: thr-instinct-${i}\nconfidence: ${confidence}\n---\n## Action\nDo threshold thing number ${i}.\n`
);
}
const baseEnv = { HOME: isoHome, USERPROFILE: isoHome, CLV2_HOMUNCULUS_DIR: homunculusDir };
try {
const def = await runScript(path.join(scriptsDir, 'session-start.js'), '', baseEnv);
assert.strictEqual(def.code, 0);
assert.ok(def.stderr.includes('Injecting 4 instinct(s)'), `default 0.7 threshold should inject only the four 0.9 instincts, stderr: ${def.stderr}`);
const raised = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_INSTINCT_CONFIDENCE_THRESHOLD: '0.95' });
assert.strictEqual(raised.code, 0);
assert.ok(!raised.stderr.includes('instinct(s) into session context'), `0.95 threshold should filter out all instincts, stderr: ${raised.stderr}`);
const lowered = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_INSTINCT_CONFIDENCE_THRESHOLD: '0.5' });
assert.strictEqual(lowered.code, 0);
assert.ok(lowered.stderr.includes('Injecting 6 instinct(s)'), `0.5 threshold should pass all eight but cap at the default 6, stderr: ${lowered.stderr}`);
// Non-decimal syntax must fall back to the default 0.7, not be read
// as hex. If "0x1" were accepted as 1.0, zero instincts would inject
// (none are at full confidence); the default 0.7 injects the four 0.9s.
const hex = await runScript(path.join(scriptsDir, 'session-start.js'), '', { ...baseEnv, ECC_INSTINCT_CONFIDENCE_THRESHOLD: '0x1' });
assert.strictEqual(hex.code, 0);
assert.ok(hex.stderr.includes('Injecting 4 instinct(s)'), `hex threshold (0x1) should fall back to the 0.7 default and inject the four 0.9 instincts, stderr: ${hex.stderr}`);
} finally {
fs.rmSync(isoHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
await asyncTest('disables session-start additional context when requested', async () => {
const isoHome = path.join(os.tmpdir(), `ecc-disabled-start-${Date.now()}`);