@unson/brainbase-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ import { type OntologyVersion, type PersonalOsOntologyAudit } from './ontology.js';
2
+ export declare function auditPersonalOsDirectory(dataDir: string, options?: {
3
+ ontologyVersion?: OntologyVersion;
4
+ }): Promise<PersonalOsOntologyAudit>;
@@ -0,0 +1,33 @@
1
+ import { auditOntology, resolveOntologyVersion } from './ontology.js';
2
+ import { loadPersonalOs } from './ssot.js';
3
+ export async function auditPersonalOsDirectory(dataDir, options = {}) {
4
+ const ontologyVersion = resolveOntologyVersion(options.ontologyVersion);
5
+ try {
6
+ return auditOntology(await loadPersonalOs(dataDir), { ontologyVersion });
7
+ }
8
+ catch (error) {
9
+ const message = error instanceof Error ? error.message : String(error);
10
+ return {
11
+ status: 'unverified',
12
+ ontologyVersion,
13
+ violationCount: null,
14
+ violations: [],
15
+ counts: null,
16
+ coverage: {
17
+ complete: false,
18
+ unavailableSources: canonicalSourcesFromError(message)
19
+ },
20
+ issues: [{
21
+ ruleId: 'ONT-AUDIT-SOURCE-UNAVAILABLE',
22
+ severity: 'error',
23
+ path: dataDir,
24
+ message
25
+ }]
26
+ };
27
+ }
28
+ }
29
+ function canonicalSourcesFromError(message) {
30
+ const sources = ['graph.json', 'relationships.json', 'personal-kg.jsonl', 'decisions.jsonl'];
31
+ const matched = sources.filter((source) => message.includes(source));
32
+ return matched.length > 0 ? matched : sources;
33
+ }
@@ -0,0 +1,181 @@
1
+ import type { DecisionRecord, PersonalOs } from './types.js';
2
+ export declare const ONTOLOGY_VERSION: "1.0.0";
3
+ export declare const SUPPORTED_ONTOLOGY_VERSIONS: readonly ["0.0.0", "1.0.0"];
4
+ export type OntologyVersion = typeof SUPPORTED_ONTOLOGY_VERSIONS[number];
5
+ export type OntologySeverity = 'error' | 'warning';
6
+ export interface OntologyViolation {
7
+ ruleId: string;
8
+ severity: OntologySeverity;
9
+ path: string;
10
+ message: string;
11
+ }
12
+ export interface OntologyAuditResult {
13
+ status: 'complete';
14
+ ontologyVersion: OntologyVersion;
15
+ violationCount: number;
16
+ violations: OntologyViolation[];
17
+ counts: {
18
+ entities: number;
19
+ relationships: number;
20
+ personalKg: number;
21
+ decisions: number;
22
+ };
23
+ coverage: {
24
+ complete: true;
25
+ unavailableSources: [];
26
+ };
27
+ issues: [];
28
+ }
29
+ export interface UnverifiedOntologyAuditResult {
30
+ status: 'unverified';
31
+ ontologyVersion: OntologyVersion;
32
+ violationCount: null;
33
+ violations: [];
34
+ counts: null;
35
+ coverage: {
36
+ complete: false;
37
+ unavailableSources: string[];
38
+ };
39
+ issues: OntologyViolation[];
40
+ }
41
+ export type PersonalOsOntologyAudit = OntologyAuditResult | UnverifiedOntologyAuditResult;
42
+ export declare const portableOntology: {
43
+ readonly version: "1.0.0";
44
+ readonly effectiveAt: "2026-08-03T00:00:00.000Z";
45
+ readonly compatibility: "read-compatible-write-gated";
46
+ readonly name: "Brainbase Portable Ontology Kernel";
47
+ readonly description: "A local-first semantic contract for Brainbase Personal OS data.";
48
+ readonly domains: {
49
+ readonly types: {
50
+ readonly concepts: readonly [{
51
+ readonly id: "person";
52
+ readonly meaning: "A human represented in the local Graph.";
53
+ readonly usageConditions: readonly ["Use only for a human identity approved for the canonical local SSOT."];
54
+ }, {
55
+ readonly id: "org";
56
+ readonly meaning: "An organization represented in the local Graph.";
57
+ readonly usageConditions: readonly ["Use for a named organizational actor, not for a project or product."];
58
+ }, {
59
+ readonly id: "project";
60
+ readonly meaning: "A bounded body of work represented in the local Graph.";
61
+ readonly usageConditions: readonly ["Use when the entity has a bounded work objective; do not use it as an organization alias."];
62
+ }, {
63
+ readonly id: "relationship";
64
+ readonly meaning: "A contextual connection to a person.";
65
+ readonly usageConditions: readonly ["The person field must resolve to a canonical person entity by name."];
66
+ }, {
67
+ readonly id: "decision";
68
+ readonly meaning: "A durable choice that may explicitly supersede another choice.";
69
+ readonly usageConditions: readonly ["Use for an explicit durable choice; replacement requires a supersedes Decision ID."];
70
+ }];
71
+ };
72
+ readonly relations: {
73
+ readonly vocabulary: readonly [{
74
+ readonly id: "relates_to";
75
+ readonly source: "relationship";
76
+ readonly target: "person";
77
+ }, {
78
+ readonly id: "supersedes";
79
+ readonly source: "decision";
80
+ readonly target: "decision";
81
+ }, {
82
+ readonly id: "about";
83
+ readonly source: "decision";
84
+ readonly target: "topic";
85
+ }];
86
+ };
87
+ readonly constraints: {
88
+ readonly rules: readonly [{
89
+ readonly id: "ONT-ENTITY-ID-UNIQUE";
90
+ readonly severity: "error";
91
+ readonly meaning: "Graph entity IDs must be unique.";
92
+ }, {
93
+ readonly id: "ONT-RELATIONSHIP-ID-UNIQUE";
94
+ readonly severity: "error";
95
+ readonly meaning: "Relationship IDs must be unique.";
96
+ }, {
97
+ readonly id: "ONT-DECISION-ID-UNIQUE";
98
+ readonly severity: "error";
99
+ readonly meaning: "Decision IDs must be unique.";
100
+ }, {
101
+ readonly id: "ONT-RELATIONSHIP-PERSON-RESOLVES";
102
+ readonly severity: "warning";
103
+ readonly meaning: "A relationship person should resolve to a canonical person entity by name.";
104
+ }, {
105
+ readonly id: "ONT-DECISION-SUPERSEDES-EXISTS";
106
+ readonly severity: "error";
107
+ readonly meaning: "A supersedes reference must resolve to an existing decision.";
108
+ }, {
109
+ readonly id: "ONT-DECISION-SUPERSEDES-SELF";
110
+ readonly severity: "error";
111
+ readonly meaning: "A decision must not supersede itself.";
112
+ }, {
113
+ readonly id: "ONT-DECISION-SUPERSEDES-CYCLE";
114
+ readonly severity: "error";
115
+ readonly meaning: "Decision supersession edges must not form a cycle.";
116
+ }];
117
+ };
118
+ readonly inference: {
119
+ readonly rules: readonly [{
120
+ readonly id: "ONT-INFER-EXPLICIT-SUPERSESSION";
121
+ readonly meaning: "Only an explicit supersedes edge makes an older decision inactive.";
122
+ }, {
123
+ readonly id: "ONT-INFER-SAME-TOPIC-CONFLICT";
124
+ readonly meaning: "Multiple active decisions on the same explicit topic are reported as a conflict.";
125
+ }];
126
+ };
127
+ readonly evolution: {
128
+ readonly compatibility: readonly [{
129
+ readonly fromVersion: "0.0.0";
130
+ readonly toVersion: "1.0.0";
131
+ readonly level: "read-compatible-write-gated";
132
+ readonly changes: readonly ["Adds a versioned public semantic contract.", "Adds optional topic, supersedes, and effectiveAt decision fields."];
133
+ readonly migration: "Before enabling 1.0.0 writes, back up the Personal OS directory and run ontology:audit with ontology version 1.0.0. Existing files remain readable, but error violations must be reviewed and repaired before a canonical write.";
134
+ readonly rollback: "For the first npm release, run npm uninstall -g @unson/brainbase-mcp, restore the MCP client configuration and launch command captured before rollout, and restart the client. For later upgrades, reinstall the recorded last known working package version. Restore the pre-upgrade Personal OS backup only if a reviewed repair changed canonical files. Merely avoiding ontology commands does not disable the write guards.";
135
+ }];
136
+ };
137
+ };
138
+ };
139
+ export declare function auditOntology(os: PersonalOs, options?: {
140
+ ontologyVersion?: OntologyVersion;
141
+ }): OntologyAuditResult;
142
+ export declare function assertOntologyValid(os: PersonalOs): void;
143
+ export interface DecisionInferenceEvidence {
144
+ ruleId: string;
145
+ sourceDecisionId?: string;
146
+ targetDecisionId?: string;
147
+ topic?: string;
148
+ decisionIds?: string[];
149
+ }
150
+ export interface DecisionInferenceResult {
151
+ status: 'empty' | 'resolved' | 'conflict' | 'invalid';
152
+ ontologyVersion: OntologyVersion;
153
+ asOf: string;
154
+ activeDecisionIds: string[];
155
+ supersededDecisionIds: string[];
156
+ conflicts: Array<{
157
+ topic: string;
158
+ decisionIds: string[];
159
+ }>;
160
+ evidence: DecisionInferenceEvidence[];
161
+ explanations: string[];
162
+ violations: OntologyViolation[];
163
+ }
164
+ export declare function inferPersonalOs(os: PersonalOs, options?: {
165
+ asOf?: string;
166
+ ontologyVersion?: OntologyVersion;
167
+ }): DecisionInferenceResult;
168
+ export declare function inferDecisions(decisions: DecisionRecord[], options?: {
169
+ asOf?: string;
170
+ ontologyVersion?: OntologyVersion;
171
+ }): DecisionInferenceResult;
172
+ export declare function getOntologyImpact(fromVersion?: string): {
173
+ fromVersion: string;
174
+ toVersion: typeof ONTOLOGY_VERSION;
175
+ supported: boolean;
176
+ compatibility: string | null;
177
+ changes: readonly string[];
178
+ migration: string | null;
179
+ rollback: string | null;
180
+ };
181
+ export declare function resolveOntologyVersion(version: string | undefined): OntologyVersion;
@@ -0,0 +1,384 @@
1
+ export const ONTOLOGY_VERSION = '1.0.0';
2
+ export const SUPPORTED_ONTOLOGY_VERSIONS = ['0.0.0', ONTOLOGY_VERSION];
3
+ const release = {
4
+ version: ONTOLOGY_VERSION,
5
+ effectiveAt: '2026-08-03T00:00:00.000Z',
6
+ compatibility: 'read-compatible-write-gated',
7
+ name: 'Brainbase Portable Ontology Kernel',
8
+ description: 'A local-first semantic contract for Brainbase Personal OS data.',
9
+ domains: {
10
+ types: {
11
+ concepts: [
12
+ {
13
+ id: 'person',
14
+ meaning: 'A human represented in the local Graph.',
15
+ usageConditions: ['Use only for a human identity approved for the canonical local SSOT.']
16
+ },
17
+ {
18
+ id: 'org',
19
+ meaning: 'An organization represented in the local Graph.',
20
+ usageConditions: ['Use for a named organizational actor, not for a project or product.']
21
+ },
22
+ {
23
+ id: 'project',
24
+ meaning: 'A bounded body of work represented in the local Graph.',
25
+ usageConditions: ['Use when the entity has a bounded work objective; do not use it as an organization alias.']
26
+ },
27
+ {
28
+ id: 'relationship',
29
+ meaning: 'A contextual connection to a person.',
30
+ usageConditions: ['The person field must resolve to a canonical person entity by name.']
31
+ },
32
+ {
33
+ id: 'decision',
34
+ meaning: 'A durable choice that may explicitly supersede another choice.',
35
+ usageConditions: ['Use for an explicit durable choice; replacement requires a supersedes Decision ID.']
36
+ }
37
+ ]
38
+ },
39
+ relations: {
40
+ vocabulary: [
41
+ { id: 'relates_to', source: 'relationship', target: 'person' },
42
+ { id: 'supersedes', source: 'decision', target: 'decision' },
43
+ { id: 'about', source: 'decision', target: 'topic' }
44
+ ]
45
+ },
46
+ constraints: {
47
+ rules: [
48
+ { id: 'ONT-ENTITY-ID-UNIQUE', severity: 'error', meaning: 'Graph entity IDs must be unique.' },
49
+ { id: 'ONT-RELATIONSHIP-ID-UNIQUE', severity: 'error', meaning: 'Relationship IDs must be unique.' },
50
+ { id: 'ONT-DECISION-ID-UNIQUE', severity: 'error', meaning: 'Decision IDs must be unique.' },
51
+ { id: 'ONT-RELATIONSHIP-PERSON-RESOLVES', severity: 'warning', meaning: 'A relationship person should resolve to a canonical person entity by name.' },
52
+ { id: 'ONT-DECISION-SUPERSEDES-EXISTS', severity: 'error', meaning: 'A supersedes reference must resolve to an existing decision.' },
53
+ { id: 'ONT-DECISION-SUPERSEDES-SELF', severity: 'error', meaning: 'A decision must not supersede itself.' },
54
+ { id: 'ONT-DECISION-SUPERSEDES-CYCLE', severity: 'error', meaning: 'Decision supersession edges must not form a cycle.' }
55
+ ]
56
+ },
57
+ inference: {
58
+ rules: [
59
+ {
60
+ id: 'ONT-INFER-EXPLICIT-SUPERSESSION',
61
+ meaning: 'Only an explicit supersedes edge makes an older decision inactive.'
62
+ },
63
+ {
64
+ id: 'ONT-INFER-SAME-TOPIC-CONFLICT',
65
+ meaning: 'Multiple active decisions on the same explicit topic are reported as a conflict.'
66
+ }
67
+ ]
68
+ },
69
+ evolution: {
70
+ compatibility: [
71
+ {
72
+ fromVersion: '0.0.0',
73
+ toVersion: ONTOLOGY_VERSION,
74
+ level: 'read-compatible-write-gated',
75
+ changes: [
76
+ 'Adds a versioned public semantic contract.',
77
+ 'Adds optional topic, supersedes, and effectiveAt decision fields.'
78
+ ],
79
+ migration: 'Before enabling 1.0.0 writes, back up the Personal OS directory and run ontology:audit with ontology version 1.0.0. Existing files remain readable, but error violations must be reviewed and repaired before a canonical write.',
80
+ rollback: 'For the first npm release, run npm uninstall -g @unson/brainbase-mcp, restore the MCP client configuration and launch command captured before rollout, and restart the client. For later upgrades, reinstall the recorded last known working package version. Restore the pre-upgrade Personal OS backup only if a reviewed repair changed canonical files. Merely avoiding ontology commands does not disable the write guards.'
81
+ }
82
+ ]
83
+ }
84
+ }
85
+ };
86
+ export const portableOntology = deepFreeze(release);
87
+ export function auditOntology(os, options = {}) {
88
+ const ontologyVersion = resolveOntologyVersion(options.ontologyVersion);
89
+ const violations = [];
90
+ // 0.0.0 names the pre-kernel legacy semantics. Canonical shape validation is
91
+ // still performed by the SSOT reader, but no 1.0.0 semantic rules are
92
+ // projected backward onto historical snapshots.
93
+ if (ontologyVersion === '0.0.0') {
94
+ return completeAudit(os, ontologyVersion, violations);
95
+ }
96
+ auditDuplicateIds(os.graph.entities, 'graph.entities', 'ONT-ENTITY-ID-UNIQUE', 'Graph entity IDs must be unique.', violations);
97
+ auditDuplicateIds(os.relationships.relationships, 'relationships.relationships', 'ONT-RELATIONSHIP-ID-UNIQUE', 'Relationship IDs must be unique.', violations);
98
+ auditDuplicateIds(os.decisions, 'decisions', 'ONT-DECISION-ID-UNIQUE', 'Decision IDs must be unique.', violations);
99
+ const personNames = new Set(os.graph.entities.filter((entity) => entity.type === 'person').map((entity) => entity.name));
100
+ os.relationships.relationships.forEach((relationship, index) => {
101
+ if (!personNames.has(relationship.person)) {
102
+ violations.push({
103
+ ruleId: 'ONT-RELATIONSHIP-PERSON-RESOLVES',
104
+ severity: 'warning',
105
+ path: `relationships.relationships[${index}].person`,
106
+ message: `Relationship person ${JSON.stringify(relationship.person)} does not resolve to a person entity.`
107
+ });
108
+ }
109
+ });
110
+ auditDecisionSupersession(os.decisions, violations);
111
+ return completeAudit(os, ontologyVersion, violations);
112
+ }
113
+ export function assertOntologyValid(os) {
114
+ const result = auditOntology(os);
115
+ const errors = result.violations.filter((violation) => violation.severity === 'error');
116
+ if (errors.length > 0) {
117
+ throw new Error(`Ontology validation failed: ${errors.map((error) => `${error.ruleId} at ${error.path}: ${error.message}`).join('; ')}`);
118
+ }
119
+ }
120
+ export function inferPersonalOs(os, options = {}) {
121
+ const ontologyVersion = resolveOntologyVersion(options.ontologyVersion);
122
+ const audit = auditOntology(os, { ontologyVersion });
123
+ const errors = audit.violations.filter((violation) => violation.severity === 'error');
124
+ if (errors.length > 0) {
125
+ return {
126
+ status: 'invalid',
127
+ ontologyVersion,
128
+ asOf: options.asOf ?? new Date().toISOString(),
129
+ activeDecisionIds: [],
130
+ supersededDecisionIds: [],
131
+ conflicts: [],
132
+ evidence: [],
133
+ explanations: ['Inference was not performed because the complete Personal OS snapshot is invalid.'],
134
+ violations: errors
135
+ };
136
+ }
137
+ return inferDecisions(os.decisions, { ...options, ontologyVersion });
138
+ }
139
+ export function inferDecisions(decisions, options = {}) {
140
+ const ontologyVersion = resolveOntologyVersion(options.ontologyVersion);
141
+ const asOf = options.asOf ?? new Date().toISOString();
142
+ if (ontologyVersion === '0.0.0') {
143
+ return {
144
+ status: decisions.length === 0 ? 'empty' : 'resolved',
145
+ ontologyVersion,
146
+ asOf,
147
+ activeDecisionIds: decisions.map((decision) => decision.id),
148
+ supersededDecisionIds: [],
149
+ conflicts: [],
150
+ evidence: [],
151
+ explanations: [
152
+ 'Ontology 0.0.0 is the pre-kernel legacy interpretation; 1.0.0 effectiveAt, supersession, and conflict rules were not applied.'
153
+ ],
154
+ violations: []
155
+ };
156
+ }
157
+ const asOfInstant = Date.parse(asOf);
158
+ if (Number.isNaN(asOfInstant)) {
159
+ return {
160
+ status: 'invalid',
161
+ ontologyVersion,
162
+ asOf,
163
+ activeDecisionIds: [],
164
+ supersededDecisionIds: [],
165
+ conflicts: [],
166
+ evidence: [],
167
+ explanations: ['Inference was not performed because asOf is not a valid ISO date-time.'],
168
+ violations: [{
169
+ ruleId: 'ONT-INFERENCE-AS-OF-DATETIME',
170
+ severity: 'error',
171
+ path: 'asOf',
172
+ message: `asOf must be a valid ISO date-time. Received: ${asOf}.`
173
+ }]
174
+ };
175
+ }
176
+ const effectiveDecisions = decisions.filter((decision) => {
177
+ if (!decision.effectiveAt)
178
+ return true;
179
+ const effectiveInstant = Date.parse(decision.effectiveAt);
180
+ return !Number.isNaN(effectiveInstant) && effectiveInstant <= asOfInstant;
181
+ });
182
+ const violations = [];
183
+ auditDuplicateIds(effectiveDecisions, 'decisions', 'ONT-DECISION-ID-UNIQUE', 'Decision IDs must be unique.', violations);
184
+ auditDecisionSupersession(effectiveDecisions, violations);
185
+ if (violations.some((violation) => violation.severity === 'error')) {
186
+ return {
187
+ status: 'invalid',
188
+ ontologyVersion,
189
+ asOf,
190
+ activeDecisionIds: [],
191
+ supersededDecisionIds: [],
192
+ conflicts: [],
193
+ evidence: [],
194
+ explanations: ['Inference was not performed because the decision graph is invalid.'],
195
+ violations
196
+ };
197
+ }
198
+ const superseded = new Set();
199
+ const evidence = [];
200
+ for (const decision of effectiveDecisions) {
201
+ for (const target of decision.supersedes ?? []) {
202
+ superseded.add(target);
203
+ evidence.push({
204
+ sourceDecisionId: decision.id,
205
+ targetDecisionId: target,
206
+ ruleId: 'ONT-INFER-EXPLICIT-SUPERSESSION'
207
+ });
208
+ }
209
+ }
210
+ const active = effectiveDecisions.filter((decision) => !superseded.has(decision.id));
211
+ const topicGroups = new Map();
212
+ for (const decision of active) {
213
+ if (!decision.topic) {
214
+ continue;
215
+ }
216
+ const ids = topicGroups.get(decision.topic) ?? [];
217
+ ids.push(decision.id);
218
+ topicGroups.set(decision.topic, ids);
219
+ }
220
+ const conflicts = [...topicGroups.entries()]
221
+ .filter(([, ids]) => ids.length > 1)
222
+ .map(([topic, decisionIds]) => ({ topic, decisionIds }));
223
+ const conflictedDecisionIds = new Set(conflicts.flatMap((conflict) => conflict.decisionIds));
224
+ const current = active.filter((decision) => !conflictedDecisionIds.has(decision.id));
225
+ evidence.push(...conflicts.map((conflict) => ({
226
+ ruleId: 'ONT-INFER-SAME-TOPIC-CONFLICT',
227
+ topic: conflict.topic,
228
+ decisionIds: conflict.decisionIds
229
+ })));
230
+ return {
231
+ status: decisions.length === 0 ? 'empty' : conflicts.length > 0 ? 'conflict' : 'resolved',
232
+ ontologyVersion,
233
+ asOf,
234
+ activeDecisionIds: current.map((decision) => decision.id),
235
+ supersededDecisionIds: effectiveDecisions.filter((decision) => superseded.has(decision.id)).map((decision) => decision.id),
236
+ conflicts,
237
+ evidence,
238
+ explanations: [
239
+ ...evidence
240
+ .filter((item) => item.ruleId === 'ONT-INFER-EXPLICIT-SUPERSESSION')
241
+ .map((item) => `${item.sourceDecisionId} explicitly supersedes ${item.targetDecisionId}.`),
242
+ ...conflicts.map((conflict) => `Topic ${JSON.stringify(conflict.topic)} has multiple current candidates, so none is active: ${conflict.decisionIds.join(', ')}.`),
243
+ ...active.filter((decision) => !decision.topic).map((decision) => `${decision.id} has no topic and remains an independent legacy decision.`)
244
+ ],
245
+ violations
246
+ };
247
+ }
248
+ export function getOntologyImpact(fromVersion = ONTOLOGY_VERSION) {
249
+ if (fromVersion === ONTOLOGY_VERSION) {
250
+ return {
251
+ fromVersion,
252
+ toVersion: ONTOLOGY_VERSION,
253
+ supported: true,
254
+ compatibility: 'identical',
255
+ changes: [],
256
+ migration: 'No migration is required.',
257
+ rollback: 'No rollback is required.'
258
+ };
259
+ }
260
+ const release = portableOntology.domains.evolution.compatibility.find((item) => item.fromVersion === fromVersion);
261
+ if (!release) {
262
+ return {
263
+ fromVersion,
264
+ toVersion: ONTOLOGY_VERSION,
265
+ supported: false,
266
+ compatibility: null,
267
+ changes: [],
268
+ migration: null,
269
+ rollback: null
270
+ };
271
+ }
272
+ return {
273
+ fromVersion,
274
+ toVersion: ONTOLOGY_VERSION,
275
+ supported: true,
276
+ compatibility: release.level,
277
+ changes: release.changes,
278
+ migration: release.migration,
279
+ rollback: release.rollback
280
+ };
281
+ }
282
+ function auditDuplicateIds(records, path, ruleId, message, violations) {
283
+ const seen = new Set();
284
+ records.forEach((record, index) => {
285
+ if (seen.has(record.id)) {
286
+ violations.push({ ruleId, severity: 'error', path: `${path}[${index}].id`, message: `${message} Duplicate: ${record.id}.` });
287
+ }
288
+ seen.add(record.id);
289
+ });
290
+ }
291
+ function completeAudit(os, ontologyVersion, violations) {
292
+ return {
293
+ status: 'complete',
294
+ ontologyVersion,
295
+ violationCount: violations.length,
296
+ violations,
297
+ counts: {
298
+ entities: os.graph.entities.length,
299
+ relationships: os.relationships.relationships.length,
300
+ personalKg: os.personalKg.length,
301
+ decisions: os.decisions.length
302
+ },
303
+ coverage: { complete: true, unavailableSources: [] },
304
+ issues: []
305
+ };
306
+ }
307
+ export function resolveOntologyVersion(version) {
308
+ const requested = version ?? ONTOLOGY_VERSION;
309
+ if (!SUPPORTED_ONTOLOGY_VERSIONS.includes(requested)) {
310
+ throw new Error(`Unsupported ontology version ${JSON.stringify(requested)}. Supported versions: ${SUPPORTED_ONTOLOGY_VERSIONS.join(', ')}.`);
311
+ }
312
+ return requested;
313
+ }
314
+ function auditDecisionSupersession(decisions, violations) {
315
+ const ids = new Set(decisions.map((decision) => decision.id));
316
+ const edges = new Map(decisions.map((decision) => [decision.id, decision.supersedes ?? []]));
317
+ decisions.forEach((decision, decisionIndex) => {
318
+ (decision.supersedes ?? []).forEach((target, targetIndex) => {
319
+ const path = `decisions[${decisionIndex}].supersedes[${targetIndex}]`;
320
+ if (!ids.has(target)) {
321
+ violations.push({
322
+ ruleId: 'ONT-DECISION-SUPERSEDES-EXISTS',
323
+ severity: 'error',
324
+ path,
325
+ message: `Superseded decision ${JSON.stringify(target)} does not exist.`
326
+ });
327
+ }
328
+ if (target === decision.id) {
329
+ violations.push({
330
+ ruleId: 'ONT-DECISION-SUPERSEDES-SELF',
331
+ severity: 'error',
332
+ path,
333
+ message: 'A decision cannot supersede itself.'
334
+ });
335
+ }
336
+ });
337
+ });
338
+ const state = new Map();
339
+ const stack = [];
340
+ const cycleNodes = new Set();
341
+ const visit = (id) => {
342
+ if (state.has(id)) {
343
+ return;
344
+ }
345
+ state.set(id, 'visiting');
346
+ stack.push(id);
347
+ for (const target of edges.get(id) ?? []) {
348
+ if (!ids.has(target)) {
349
+ continue;
350
+ }
351
+ if (state.get(target) === 'visiting') {
352
+ const cycleStart = stack.indexOf(target);
353
+ for (const cycleId of stack.slice(cycleStart)) {
354
+ cycleNodes.add(cycleId);
355
+ }
356
+ }
357
+ else if (!state.has(target)) {
358
+ visit(target);
359
+ }
360
+ }
361
+ stack.pop();
362
+ state.set(id, 'visited');
363
+ };
364
+ for (const id of ids) {
365
+ visit(id);
366
+ }
367
+ for (const id of cycleNodes) {
368
+ violations.push({
369
+ ruleId: 'ONT-DECISION-SUPERSEDES-CYCLE',
370
+ severity: 'error',
371
+ path: `decisions.${id}.supersedes`,
372
+ message: `Decision supersession cycle includes ${id}.`
373
+ });
374
+ }
375
+ }
376
+ function deepFreeze(value) {
377
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
378
+ Object.freeze(value);
379
+ for (const child of Object.values(value)) {
380
+ deepFreeze(child);
381
+ }
382
+ }
383
+ return value;
384
+ }
@@ -0,0 +1,24 @@
1
+ export type OperationalizationTarget = 'codex' | 'claude' | 'codecode' | 'agent';
2
+ export interface OperationalizationAction {
3
+ id: 'public-skills' | 'routines' | 'mcp-config' | 'source-allowlist' | 'verification';
4
+ title: string;
5
+ status: 'pending';
6
+ command: string;
7
+ why: string;
8
+ safety: string;
9
+ }
10
+ export interface OperationalizationPlan {
11
+ goal: string;
12
+ completed: string[];
13
+ pending: OperationalizationAction[];
14
+ recommendedOrder: string[];
15
+ safetyRules: string[];
16
+ completionCheck: string[];
17
+ }
18
+ export interface OperationalizationPlanInput {
19
+ target?: OperationalizationTarget;
20
+ dataDir?: string;
21
+ cwd?: string;
22
+ firstValueReady?: boolean;
23
+ }
24
+ export declare function buildOperationalizationPlan(input?: OperationalizationPlanInput): OperationalizationPlan;