feat(ms-ai-architect): C2.1 lib/user-data resolver + ambient org-injeksjon + C1 config bakoverkompat (TDD) [skip-docs]

Bæreren i C2 onboarding-redesign (#26). TDD: test FØR kode.

- lib/user-data.mjs (NY, ren, SKRIVER ALDRI; kun node:os/path):
  resolveUserDataDir/resolveOrgDir/resolveConfigPath peker ~/.claude/ms-ai-architect/
  (uavhengig av pluginRoot -> overlever reinstall, K2). CONFIG_FILENAME delt med C1
  (en sannhetskilde). buildOrgSummary: deterministisk, lengde-kappet H2-ekstraksjon.
- detection-schedule.mjs: loadScheduleConfig(pluginRoot, home=homedir()) leser
  bruker-sti FORST, fallback plugin-rot. Lokal CONFIG_FILENAME -> importeres fra resolver.
  Alle 3 kallere bruker ett-arg-kall (uendret oppforsel).
- session-start-context.mjs: injiserer buildOrgSummary ambient (K1) som egen
  Virksomhetskontekst-blokk; status-nudge beholdt nar org tom.

Verifisert: user-data 16/16 · detection-schedule 23/23 · test-hooks 10/10 ·
kb-update 191 · kb-eval 100 · validate PASSED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-22 13:34:05 +02:00
commit dc0b627e98
6 changed files with 459 additions and 31 deletions

View file

@ -25,6 +25,7 @@ import {
summarizeSkillLifecycle,
daysSinceLastPoll,
} from '../../scripts/kb-update/lib/detection-schedule.mjs';
import { resolveConfigPath, resolveUserDataDir } from '../../scripts/kb-update/lib/user-data.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = join(__dirname, '..', '..');
@ -104,27 +105,66 @@ test('daysSinceLastPoll — computes whole days since last_poll', () => {
assert.equal(daysSinceLastPoll({ last_poll: tenDaysAgo }, now), 10);
});
test('loadScheduleConfig — missing config file => OFF default', () => {
test('loadScheduleConfig — no config (neither user nor plugin-root) => OFF default', () => {
const root = mkdtempSync(join(tmpdir(), 'sched-'));
const home = mkdtempSync(join(tmpdir(), 'home-')); // empty home: no user-owned config
try {
assert.deepEqual(loadScheduleConfig(root), DEFAULT_SCHEDULE_CONFIG);
assert.deepEqual(loadScheduleConfig(root, home), DEFAULT_SCHEDULE_CONFIG);
} finally {
rmSync(root, { recursive: true, force: true });
rmSync(home, { recursive: true, force: true });
}
});
test('loadScheduleConfig — reads ms-ai-architect.local.md when present', () => {
test('loadScheduleConfig — reads plugin-root ms-ai-architect.local.md when no user config (fallback)', () => {
const root = mkdtempSync(join(tmpdir(), 'sched-'));
const home = mkdtempSync(join(tmpdir(), 'home-'));
try {
writeFileSync(
join(root, 'ms-ai-architect.local.md'),
'---\nscheduled_detection:\n enabled: true\n interval_days: 14\n---\n',
);
const cfg = loadScheduleConfig(root);
const cfg = loadScheduleConfig(root, home);
assert.equal(cfg.enabled, true);
assert.equal(cfg.interval_days, 14);
} finally {
rmSync(root, { recursive: true, force: true });
rmSync(home, { recursive: true, force: true });
}
});
// --- C2.1: user-owned config wins; plugin-root is the backward-compat fallback ---
function writeUserConfig(home, text) {
mkdirSync(resolveUserDataDir(home), { recursive: true });
writeFileSync(resolveConfigPath(home), text);
}
test('loadScheduleConfig — user-owned config is read even when pluginRoot has none (K2 survival)', () => {
const root = mkdtempSync(join(tmpdir(), 'sched-')); // fresh reinstall: plugin root has no config
const home = mkdtempSync(join(tmpdir(), 'home-'));
try {
writeUserConfig(home, '---\nscheduled_detection:\n enabled: true\n interval_days: 5\n---\n');
const cfg = loadScheduleConfig(root, home);
assert.equal(cfg.enabled, true, 'config survives in the user dir, independent of pluginRoot');
assert.equal(cfg.interval_days, 5);
} finally {
rmSync(root, { recursive: true, force: true });
rmSync(home, { recursive: true, force: true });
}
});
test('loadScheduleConfig — user config wins over a plugin-root config', () => {
const root = mkdtempSync(join(tmpdir(), 'sched-'));
const home = mkdtempSync(join(tmpdir(), 'home-'));
try {
writeFileSync(join(root, 'ms-ai-architect.local.md'), 'scheduled_detection:\n enabled: true\n interval_days: 99\n');
writeUserConfig(home, 'scheduled_detection:\n enabled: true\n interval_days: 3\n');
const cfg = loadScheduleConfig(root, home);
assert.equal(cfg.interval_days, 3, 'user path takes precedence over plugin-root');
} finally {
rmSync(root, { recursive: true, force: true });
rmSync(home, { recursive: true, force: true });
}
});