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

@ -8,10 +8,14 @@
// manual, in-session, and gated. This module is the pure decision core for the
// SessionStart hook + the run-detection.mjs executor.
//
// Zero dependencies beyond node:fs/path. parseScheduleConfig is pure.
// parseScheduleConfig is pure (text in, config out). loadScheduleConfig resolves
// paths via lib/user-data.mjs (which itself depends only on node:os/path), so
// the user-owned config dir is the single source of truth shared with the hook.
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
import { resolveConfigPath, CONFIG_FILENAME } from './user-data.mjs';
// Opt-in: detection is OFF until the user sets enabled:true. "Frivillig å sette
// opp — men gjøres det, skal det virke." (operator 2026-06-20)
@ -36,8 +40,6 @@ export const DETECTION_STEPS = Object.freeze([
{ name: 'detect-skill-lifecycle', dir: 'kb-eval', script: 'detect-skill-lifecycle.mjs', args: ['--write'], skillLifecycle: true },
]);
const CONFIG_FILENAME = 'ms-ai-architect.local.md';
/** Coerce a YAML-ish scalar to bool/number/string. Unknown booleans => false. */
function coerce(key, raw) {
const v = raw.trim();
@ -87,19 +89,26 @@ export function parseScheduleConfig(text) {
}
/**
* Load the opt-in schedule config from <pluginRoot>/ms-ai-architect.local.md
* (gitignored per the plugin-settings pattern). Absent/unreadable => OFF default.
* Load the opt-in schedule config. C2.1: the user-owned path
* (~/.claude/ms-ai-architect/ms-ai-architect.local.md) takes precedence so the
* config survives a plugin reinstall (acceptance K2); the legacy
* <pluginRoot>/ms-ai-architect.local.md remains as a backward-compat fallback
* for setups that already wrote it there. Absent/unreadable => OFF default.
* @param {string} pluginRoot
* @param {string} [home] injectable home dir (defaults to os.homedir())
* @returns {{enabled: boolean, interval_days: number, include_skill_lifecycle: boolean}}
*/
export function loadScheduleConfig(pluginRoot) {
const path = join(pluginRoot, CONFIG_FILENAME);
if (!existsSync(path)) return { ...DEFAULT_SCHEDULE_CONFIG };
try {
return parseScheduleConfig(readFileSync(path, 'utf8'));
} catch {
return { ...DEFAULT_SCHEDULE_CONFIG };
export function loadScheduleConfig(pluginRoot, home = homedir()) {
const candidates = [resolveConfigPath(home), join(pluginRoot, CONFIG_FILENAME)];
for (const path of candidates) {
if (!existsSync(path)) continue;
try {
return parseScheduleConfig(readFileSync(path, 'utf8'));
} catch {
return { ...DEFAULT_SCHEDULE_CONFIG };
}
}
return { ...DEFAULT_SCHEDULE_CONFIG };
}
/**