'use strict'; const { loadContextRegistry, projectionFor, explainContextEntry } = require('./context-pack-registry'); const { DEFAULT_REPO_ROOT, compilerDigest, createSourceReader, digestObject, normalizeMetadataText, stableStringify, validateSchema, validateTarget, } = require('./context-profile-support'); const PROFILE_ALIASES = Object.freeze({ lean: 'lean@1', full: 'full@1' }); const MODES = Object.freeze(['manual', 'suggest', 'auto']); function loadContextProfile(profileId = 'lean@1', { repoRoot = DEFAULT_REPO_ROOT } = {}) { const id = PROFILE_ALIASES[profileId] || profileId; if (!['lean@1', 'full@1'].includes(id)) throw new Error(`Unknown context profile: ${profileId}`); const source = createSourceReader(repoRoot).json(`manifests/context-profiles/${id}.json`); validateSchema(source, 'context-profile.schema.json'); if (source.id !== id) throw new Error('Context profile source ID does not match the requested profile'); if ((id === 'lean@1' && (source.budget.mode !== 'blocking' || source.selection.eager === 'all')) || (id === 'full@1' && (source.budget.mode !== 'report-only' || source.selection.eager !== 'all'))) { throw new Error('Profile selection and budget mode violate the versioned profile contract'); } const canonical = { ...source, description: normalizeMetadataText(source.description, 'Profile description'), selection: { ...source.selection, eager: source.selection.eager === 'all' ? 'all' : [...source.selection.eager].sort(), required: [...source.selection.required].sort(), }, }; return { ...canonical, profileDigest: digestObject(canonical) }; } function validateSelectors(values, knownIds, label) { if (!Array.isArray(values)) throw new Error(`${label} must be an array of skill IDs`); const seen = new Set(); for (const id of values) { if (typeof id !== 'string' || !knownIds.has(id)) throw new Error(`Unknown ${label} ID: ${id}`); if (seen.has(id)) throw new Error(`Duplicate ${label} ID: ${id}`); seen.add(id); } return [...seen].sort(); } function resolveSelection(registry, profile, include, exclude) { const byId = new Map(registry.entries.map(entry => [entry.id, entry])); const known = new Set(byId.keys()); const additions = validateSelectors(include, known, 'include'); const removals = new Set(validateSelectors(exclude, known, 'exclude')); const eager = profile.selection.eager === 'all' ? [...known] : validateSelectors(profile.selection.eager, known, 'profile'); const required = validateSelectors(profile.selection.required, known, 'required'); for (const id of required) { if (!eager.includes(id)) throw new Error(`Profile is missing required eager ID: ${id}`); if (removals.has(id)) throw new Error(`Cannot exclude required profile entry: ${id}`); } if (additions.some(id => removals.has(id))) throw new Error('Include and exclude selections overlap'); const selected = new Map(); function select(id, reason) { if (removals.has(id)) throw new Error(`Required dependency closure excludes ${id}`); if (selected.has(id)) return; selected.set(id, reason); byId.get(id).dependencies.forEach(dependency => select(dependency, `Required dependency of ${id}`)); } eager.filter(id => !removals.has(id)).sort().forEach(id => select(id, 'Selected by context profile')); additions.forEach(id => select(id, 'Explicitly included')); return registry.entries.map(entry => ({ ...entry, selection: selected.has(entry.id) ? 'selected' : removals.has(entry.id) ? 'excluded' : 'routed', reason: selected.get(entry.id) || (removals.has(entry.id) ? 'Explicitly excluded' : 'Available through routed discovery'), })); } function estimateMetadata(entries, target, profile) { const ledger = entries.filter(entry => entry.selection === 'selected').map(entry => { const metadata = { harness: target, type: 'skill', name: entry.name, description: entry.description }; const renderedBytes = Buffer.byteLength(`${stableStringify(metadata)}\n`, 'utf8'); return { id: entry.id, renderedBytes, estimatedTokens: Math.ceil(renderedBytes / 4) }; }); const estimatedTokens = ledger.reduce((total, entry) => total + entry.estimatedTokens, 0); return { method: 'utf8-bytes-div-4@1', surface: 'skill-discovery-metadata', renderedBytes: ledger.reduce((total, entry) => total + entry.renderedBytes, 0), estimatedTokens, budgetTokens: profile.budget.tokens, withinBudget: estimatedTokens <= profile.budget.tokens, budgetMode: profile.budget.mode, nativeTokens: null, wrapperTokens: null, wholeScopeTokens: null, ledger, }; } function compileContextProfile({ repoRoot = DEFAULT_REPO_ROOT, profileId = 'lean@1', selectionMode = 'manual', target = 'codex', include = [], exclude = [], } = {}) { validateTarget(target); if (!MODES.includes(selectionMode)) throw new Error(`Unknown selection mode: ${selectionMode}`); const registry = loadContextRegistry({ repoRoot }); const profile = loadContextProfile(profileId, { repoRoot }); if (profile.registryId !== registry.id) throw new Error('Profile registry ID mismatch'); const selected = resolveSelection(registry, profile, include, exclude); const ids = selection => selected.filter(entry => entry.selection === selection).map(entry => entry.id); const value = { schemaVersion: 'ecc.context-plan.v1', profileId: profile.id, selectionMode, target, disposition: 'proposed', active: false, registryDigest: registry.registryDigest, profileDigest: profile.profileDigest, compilerDigest: compilerDigest(), selectedIds: ids('selected'), routedIds: ids('routed'), excludedIds: ids('excluded'), entries: selected.map(entry => ({ id: entry.id, selection: entry.selection, reason: entry.reason, sourcePath: entry.sourcePath, contentDigest: entry.contentDigest, requiredResources: [...entry.requiredResources], projection: projectionFor(entry, target), })), estimate: estimateMetadata(selected, target, profile), excludedSurfaces: registry.excludedSurfaces, limitations: [ 'Read-only proposal; no harness activation, installation or permission change was attempted.', 'Selection modes are recorded intent; task routing and automatic switching are not implemented.', 'Only skill discovery metadata is estimated; provider counters, wrappers and whole-scope costs are unknown.', 'An estimate within 8000 tokens does not certify native context usage or successful discovery.', 'Dependency closure covers explicit declarations only; workflow dependency review is incomplete.', 'Install support is an owner-module declaration; it does not prove native exposure or execution.', ], }; const plan = { ...value, planDigest: digestObject(value) }; if (!plan.estimate.withinBudget && plan.estimate.budgetMode === 'blocking') { const error = new Error(`Context metadata estimate ${plan.estimate.estimatedTokens} exceeds the 8000-token ceiling`); error.code = 'CONTEXT_PROFILE_BUDGET_EXCEEDED'; error.plan = plan; throw error; } return plan; } module.exports = { compileContextProfile, explainContextEntry, loadContextProfile };