@aaronsb/kg-cli 0.7.0 → 0.8.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.
- package/dist/api/client.d.ts +42 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +119 -0
- package/dist/api/client.js.map +1 -1
- package/dist/cli/ontology.d.ts.map +1 -1
- package/dist/cli/ontology.js +534 -1
- package/dist/cli/ontology.js.map +1 -1
- package/dist/mcp/formatters/index.d.ts +1 -0
- package/dist/mcp/formatters/index.d.ts.map +1 -1
- package/dist/mcp/formatters/index.js +11 -1
- package/dist/mcp/formatters/index.js.map +1 -1
- package/dist/mcp/formatters/ontology.d.ts +93 -0
- package/dist/mcp/formatters/ontology.d.ts.map +1 -0
- package/dist/mcp/formatters/ontology.js +328 -0
- package/dist/mcp/formatters/ontology.js.map +1 -0
- package/dist/mcp/graph-operations.js +5 -5
- package/dist/mcp/graph-operations.js.map +1 -1
- package/dist/mcp-server.js +168 -8
- package/dist/mcp-server.js.map +1 -1
- package/dist/types/index.d.ts +136 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +3 -3
- package/dist/version.js +3 -3
- package/package.json +1 -1
package/dist/cli/ontology.js
CHANGED
|
@@ -68,6 +68,13 @@ exports.ontologyCommand = (0, help_formatter_1.setCommandHelp)(new commander_1.C
|
|
|
68
68
|
width: 'flex',
|
|
69
69
|
priority: 3
|
|
70
70
|
},
|
|
71
|
+
{
|
|
72
|
+
header: 'State',
|
|
73
|
+
field: 'lifecycle_state',
|
|
74
|
+
type: 'text',
|
|
75
|
+
width: 8,
|
|
76
|
+
align: 'left'
|
|
77
|
+
},
|
|
71
78
|
{
|
|
72
79
|
header: 'Files',
|
|
73
80
|
field: 'file_count',
|
|
@@ -91,7 +98,10 @@ exports.ontologyCommand = (0, help_formatter_1.setCommandHelp)(new commander_1.C
|
|
|
91
98
|
}
|
|
92
99
|
]
|
|
93
100
|
});
|
|
94
|
-
table.print(result.ontologies
|
|
101
|
+
table.print(result.ontologies.map(o => ({
|
|
102
|
+
...o,
|
|
103
|
+
lifecycle_state: o.lifecycle_state ?? '—',
|
|
104
|
+
})));
|
|
95
105
|
}
|
|
96
106
|
catch (error) {
|
|
97
107
|
console.error(colors.status.error('✗ Failed to list ontologies'));
|
|
@@ -116,6 +126,23 @@ exports.ontologyCommand = (0, help_formatter_1.setCommandHelp)(new commander_1.C
|
|
|
116
126
|
console.log(` ${colors.stats.label('Concepts:')} ${(0, colors_1.coloredCount)(info.statistics.concept_count)}`);
|
|
117
127
|
console.log(` ${colors.stats.label('Evidence:')} ${(0, colors_1.coloredCount)(info.statistics.instance_count)}`);
|
|
118
128
|
console.log(` ${colors.stats.label('Relationships:')} ${(0, colors_1.coloredCount)(info.statistics.relationship_count)}`);
|
|
129
|
+
// ADR-200: Node properties
|
|
130
|
+
if (info.node) {
|
|
131
|
+
console.log('\n' + colors.stats.section('Graph Node'));
|
|
132
|
+
console.log(` ${colors.stats.label('ID:')} ${info.node.ontology_id}`);
|
|
133
|
+
console.log(` ${colors.stats.label('State:')} ${info.node.lifecycle_state}`);
|
|
134
|
+
console.log(` ${colors.stats.label('Epoch:')} ${(0, colors_1.coloredCount)(info.node.creation_epoch)}`);
|
|
135
|
+
console.log(` ${colors.stats.label('Embedding:')} ${info.node.has_embedding ? colors.status.success('yes') : colors.status.dim('no')}`);
|
|
136
|
+
if (info.node.created_by) {
|
|
137
|
+
console.log(` ${colors.stats.label('Created By:')} ${info.node.created_by}`);
|
|
138
|
+
}
|
|
139
|
+
if (info.node.description) {
|
|
140
|
+
console.log(` ${colors.stats.label('Description:')} ${info.node.description}`);
|
|
141
|
+
}
|
|
142
|
+
if (info.node.search_terms.length > 0) {
|
|
143
|
+
console.log(` ${colors.stats.label('Search terms:')} ${info.node.search_terms.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
119
146
|
if (info.files.length > 0) {
|
|
120
147
|
console.log('\n' + colors.ui.header('Files'));
|
|
121
148
|
console.log((0, colors_1.separator)(80, '─'));
|
|
@@ -147,6 +174,9 @@ exports.ontologyCommand = (0, help_formatter_1.setCommandHelp)(new commander_1.C
|
|
|
147
174
|
console.log(colors.evidence.document(file.file_path));
|
|
148
175
|
console.log(` ${colors.ui.key('Chunks:')} ${(0, colors_1.coloredCount)(file.chunk_count)}`);
|
|
149
176
|
console.log(` ${colors.ui.key('Concepts:')} ${(0, colors_1.coloredCount)(file.concept_count)}`);
|
|
177
|
+
if (file.source_ids?.length) {
|
|
178
|
+
console.log(` ${colors.ui.key('Source IDs:')} ${file.source_ids.join(', ')}`);
|
|
179
|
+
}
|
|
150
180
|
console.log();
|
|
151
181
|
});
|
|
152
182
|
console.log((0, colors_1.separator)());
|
|
@@ -156,6 +186,65 @@ exports.ontologyCommand = (0, help_formatter_1.setCommandHelp)(new commander_1.C
|
|
|
156
186
|
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
157
187
|
process.exit(1);
|
|
158
188
|
}
|
|
189
|
+
}))
|
|
190
|
+
.addCommand(new commander_1.Command('create')
|
|
191
|
+
.description('Create an ontology before ingesting any documents (ADR-200: directed growth). This pre-creates the Ontology graph node with an embedding, making the ontology discoverable in the vector space immediately. Useful for planning knowledge domains before populating them.')
|
|
192
|
+
.showHelpAfterError()
|
|
193
|
+
.argument('<name>', 'Ontology name')
|
|
194
|
+
.option('-d, --description <text>', 'What this knowledge domain covers')
|
|
195
|
+
.action(async (name, options) => {
|
|
196
|
+
try {
|
|
197
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
198
|
+
const result = await client.createOntology(name, options.description || '');
|
|
199
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
200
|
+
console.log(colors.status.success(`✓ Created ontology "${result.name}"`));
|
|
201
|
+
console.log((0, colors_1.separator)());
|
|
202
|
+
console.log(` ${colors.ui.key('ID:')} ${result.ontology_id}`);
|
|
203
|
+
console.log(` ${colors.ui.key('State:')} ${result.lifecycle_state}`);
|
|
204
|
+
console.log(` ${colors.ui.key('Embedding:')} ${result.has_embedding ? colors.status.success('generated') : colors.status.dim('none')}`);
|
|
205
|
+
if (result.description) {
|
|
206
|
+
console.log(` ${colors.ui.key('Description:')} ${result.description}`);
|
|
207
|
+
}
|
|
208
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
console.error(colors.status.error('✗ Failed to create ontology'));
|
|
212
|
+
const detail = error.response?.data?.detail || error.message;
|
|
213
|
+
console.error(colors.status.error(detail));
|
|
214
|
+
if (detail.includes('already exists')) {
|
|
215
|
+
console.error(colors.status.dim('\n Hint: Use "kg ontology info ' + name + '" to see the existing ontology'));
|
|
216
|
+
}
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}))
|
|
220
|
+
.addCommand(new commander_1.Command('lifecycle')
|
|
221
|
+
.description('Change ontology lifecycle state (ADR-200 Phase 2). States: active (normal), pinned (immune to demotion), frozen (read-only — rejects ingest and rename).')
|
|
222
|
+
.showHelpAfterError()
|
|
223
|
+
.argument('<name>', 'Ontology name')
|
|
224
|
+
.argument('<state>', 'Target state: active, pinned, or frozen')
|
|
225
|
+
.action(async (name, state) => {
|
|
226
|
+
const validStates = ['active', 'pinned', 'frozen'];
|
|
227
|
+
if (!validStates.includes(state)) {
|
|
228
|
+
console.error(colors.status.error(`✗ Invalid state "${state}". Must be one of: ${validStates.join(', ')}`));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
233
|
+
const result = await client.updateOntologyLifecycle(name, state);
|
|
234
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
235
|
+
if (result.previous_state === result.new_state) {
|
|
236
|
+
console.log(colors.status.dim(` Ontology "${name}" is already ${result.new_state} (no-op)`));
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.log(colors.status.success(`✓ Ontology "${name}" lifecycle: ${result.previous_state} → ${result.new_state}`));
|
|
240
|
+
}
|
|
241
|
+
console.log((0, colors_1.separator)());
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
console.error(colors.status.error('✗ Failed to update ontology lifecycle'));
|
|
245
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
159
248
|
}))
|
|
160
249
|
.addCommand(new commander_1.Command('rename')
|
|
161
250
|
.description('Rename an ontology while preserving all its data (concepts, sources, relationships). This is a non-destructive operation useful for reorganization, archiving old ontologies, fixing typos, or improving clarity. Atomic transaction ensures all-or-nothing updates. Requires confirmation unless -y flag is used.')
|
|
@@ -227,5 +316,449 @@ exports.ontologyCommand = (0, help_formatter_1.setCommandHelp)(new commander_1.C
|
|
|
227
316
|
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
228
317
|
process.exit(1);
|
|
229
318
|
}
|
|
319
|
+
}))
|
|
320
|
+
// ADR-200 Phase 3a: Scoring & Annealing Control Surface
|
|
321
|
+
.addCommand(new commander_1.Command('scores')
|
|
322
|
+
.description('Show cached annealing scores for an ontology (or all ontologies). Shows mass, coherence, exposure, and protection scores. Use "kg ontology score <name>" to recompute.')
|
|
323
|
+
.showHelpAfterError()
|
|
324
|
+
.argument('[name]', 'Ontology name (omit for all)')
|
|
325
|
+
.action(async (name) => {
|
|
326
|
+
try {
|
|
327
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
328
|
+
if (name) {
|
|
329
|
+
const scores = await client.getOntologyScores(name);
|
|
330
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
331
|
+
console.log(colors.ui.title(`📊 Scores: ${name}`));
|
|
332
|
+
console.log((0, colors_1.separator)());
|
|
333
|
+
console.log(` ${colors.stats.label('Mass:')} ${scores.mass_score.toFixed(4)}`);
|
|
334
|
+
console.log(` ${colors.stats.label('Coherence:')} ${scores.coherence_score.toFixed(4)}`);
|
|
335
|
+
console.log(` ${colors.stats.label('Raw Exposure:')} ${scores.raw_exposure.toFixed(4)}`);
|
|
336
|
+
console.log(` ${colors.stats.label('Weighted Exposure:')} ${scores.weighted_exposure.toFixed(4)}`);
|
|
337
|
+
console.log(` ${colors.stats.label('Protection:')} ${scores.protection_score.toFixed(4)}`);
|
|
338
|
+
console.log(` ${colors.stats.label('Last Evaluated:')} epoch ${scores.last_evaluated_epoch}`);
|
|
339
|
+
console.log((0, colors_1.separator)());
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const result = await client.computeAllOntologyScores();
|
|
343
|
+
console.log('\n' + colors.ui.title(`📊 All Ontology Scores (epoch ${result.global_epoch})`));
|
|
344
|
+
if (result.count === 0) {
|
|
345
|
+
console.log(colors.status.warning('\n⚠ No ontologies found'));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const table = new table_1.Table({
|
|
349
|
+
columns: [
|
|
350
|
+
{ header: 'Ontology', field: 'ontology', type: 'heading', width: 'flex', priority: 3 },
|
|
351
|
+
{ header: 'Mass', field: 'mass_score', type: 'text', width: 8, align: 'right' },
|
|
352
|
+
{ header: 'Cohere', field: 'coherence_score', type: 'text', width: 8, align: 'right' },
|
|
353
|
+
{ header: 'Exposure', field: 'weighted_exposure', type: 'text', width: 10, align: 'right' },
|
|
354
|
+
{ header: 'Protect', field: 'protection_score', type: 'text', width: 10, align: 'right' },
|
|
355
|
+
{ header: 'Epoch', field: 'last_evaluated_epoch', type: 'count', width: 8, align: 'right' },
|
|
356
|
+
]
|
|
357
|
+
});
|
|
358
|
+
table.print(result.scores.map(s => ({
|
|
359
|
+
...s,
|
|
360
|
+
mass_score: s.mass_score.toFixed(3),
|
|
361
|
+
coherence_score: s.coherence_score.toFixed(3),
|
|
362
|
+
weighted_exposure: s.weighted_exposure.toFixed(3),
|
|
363
|
+
protection_score: s.protection_score.toFixed(3),
|
|
364
|
+
})));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error(colors.status.error('✗ Failed to get ontology scores'));
|
|
369
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
}))
|
|
373
|
+
.addCommand(new commander_1.Command('score')
|
|
374
|
+
.description('Recompute annealing scores for one ontology. Runs mass, coherence, exposure, and protection scoring and caches results.')
|
|
375
|
+
.showHelpAfterError()
|
|
376
|
+
.argument('<name>', 'Ontology name')
|
|
377
|
+
.action(async (name) => {
|
|
378
|
+
try {
|
|
379
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
380
|
+
const scores = await client.computeOntologyScores(name);
|
|
381
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
382
|
+
console.log(colors.status.success(`✓ Scored ontology "${name}"`));
|
|
383
|
+
console.log((0, colors_1.separator)());
|
|
384
|
+
console.log(` ${colors.stats.label('Mass:')} ${scores.mass_score.toFixed(4)}`);
|
|
385
|
+
console.log(` ${colors.stats.label('Coherence:')} ${scores.coherence_score.toFixed(4)}`);
|
|
386
|
+
console.log(` ${colors.stats.label('Raw Exposure:')} ${scores.raw_exposure.toFixed(4)}`);
|
|
387
|
+
console.log(` ${colors.stats.label('Weighted Exposure:')} ${scores.weighted_exposure.toFixed(4)}`);
|
|
388
|
+
console.log(` ${colors.stats.label('Protection:')} ${scores.protection_score.toFixed(4)}`);
|
|
389
|
+
console.log(` ${colors.stats.label('Evaluated at:')} epoch ${scores.last_evaluated_epoch}`);
|
|
390
|
+
console.log((0, colors_1.separator)());
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.error(colors.status.error('✗ Failed to score ontology'));
|
|
394
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
}))
|
|
398
|
+
.addCommand(new commander_1.Command('score-all')
|
|
399
|
+
.description('Recompute annealing scores for all ontologies. Runs full scoring pipeline and caches results on each Ontology node.')
|
|
400
|
+
.showHelpAfterError()
|
|
401
|
+
.action(async () => {
|
|
402
|
+
try {
|
|
403
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
404
|
+
const result = await client.computeAllOntologyScores();
|
|
405
|
+
console.log('\n' + colors.status.success(`✓ Scored ${result.count} ontologies (epoch ${result.global_epoch})`));
|
|
406
|
+
if (result.count > 0) {
|
|
407
|
+
const table = new table_1.Table({
|
|
408
|
+
columns: [
|
|
409
|
+
{ header: 'Ontology', field: 'ontology', type: 'heading', width: 'flex', priority: 3 },
|
|
410
|
+
{ header: 'Mass', field: 'mass_score', type: 'text', width: 8, align: 'right' },
|
|
411
|
+
{ header: 'Cohere', field: 'coherence_score', type: 'text', width: 8, align: 'right' },
|
|
412
|
+
{ header: 'Exposure', field: 'weighted_exposure', type: 'text', width: 10, align: 'right' },
|
|
413
|
+
{ header: 'Protect', field: 'protection_score', type: 'text', width: 10, align: 'right' },
|
|
414
|
+
]
|
|
415
|
+
});
|
|
416
|
+
table.print(result.scores.map(s => ({
|
|
417
|
+
...s,
|
|
418
|
+
mass_score: s.mass_score.toFixed(3),
|
|
419
|
+
coherence_score: s.coherence_score.toFixed(3),
|
|
420
|
+
weighted_exposure: s.weighted_exposure.toFixed(3),
|
|
421
|
+
protection_score: s.protection_score.toFixed(3),
|
|
422
|
+
})));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
console.error(colors.status.error('✗ Failed to score all ontologies'));
|
|
427
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
}))
|
|
431
|
+
.addCommand(new commander_1.Command('candidates')
|
|
432
|
+
.description('Show top concepts by degree centrality in an ontology. High-degree concepts are potential promotion candidates — they may warrant their own ontology.')
|
|
433
|
+
.showHelpAfterError()
|
|
434
|
+
.argument('<name>', 'Ontology name')
|
|
435
|
+
.option('-l, --limit <n>', 'Max concepts', '20')
|
|
436
|
+
.action(async (name, options) => {
|
|
437
|
+
try {
|
|
438
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
439
|
+
const result = await client.getOntologyCandidates(name, parseInt(options.limit));
|
|
440
|
+
console.log('\n' + colors.ui.title(`🎯 Promotion Candidates: ${name}`));
|
|
441
|
+
if (result.count === 0) {
|
|
442
|
+
console.log(colors.status.warning('\n⚠ No concepts found'));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const table = new table_1.Table({
|
|
446
|
+
columns: [
|
|
447
|
+
{ header: 'Concept', field: 'label', type: 'heading', width: 'flex', priority: 3 },
|
|
448
|
+
{ header: 'Degree', field: 'degree', type: 'count', width: 10, align: 'right' },
|
|
449
|
+
{ header: 'In', field: 'in_degree', type: 'count', width: 8, align: 'right' },
|
|
450
|
+
{ header: 'Out', field: 'out_degree', type: 'count', width: 8, align: 'right' },
|
|
451
|
+
]
|
|
452
|
+
});
|
|
453
|
+
table.print(result.concepts);
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
console.error(colors.status.error('✗ Failed to get candidates'));
|
|
457
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
}))
|
|
461
|
+
.addCommand(new commander_1.Command('affinity')
|
|
462
|
+
.description('Show cross-ontology concept overlap. Identifies which other ontologies share concepts with this one, ranked by affinity score.')
|
|
463
|
+
.showHelpAfterError()
|
|
464
|
+
.argument('<name>', 'Ontology name')
|
|
465
|
+
.option('-l, --limit <n>', 'Max ontologies', '10')
|
|
466
|
+
.action(async (name, options) => {
|
|
467
|
+
try {
|
|
468
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
469
|
+
const result = await client.getOntologyAffinity(name, parseInt(options.limit));
|
|
470
|
+
console.log('\n' + colors.ui.title(`🔗 Cross-Ontology Affinity: ${name}`));
|
|
471
|
+
if (result.count === 0) {
|
|
472
|
+
console.log(colors.status.warning('\n⚠ No cross-ontology connections found'));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const table = new table_1.Table({
|
|
476
|
+
columns: [
|
|
477
|
+
{ header: 'Other Ontology', field: 'other_ontology', type: 'heading', width: 'flex', priority: 3 },
|
|
478
|
+
{ header: 'Shared', field: 'shared_concept_count', type: 'count', width: 10, align: 'right' },
|
|
479
|
+
{ header: 'Total', field: 'total_concepts', type: 'count', width: 10, align: 'right' },
|
|
480
|
+
{ header: 'Affinity', field: 'affinity_score', type: 'text', width: 10, align: 'right' },
|
|
481
|
+
]
|
|
482
|
+
});
|
|
483
|
+
table.print(result.affinities.map(a => ({
|
|
484
|
+
...a,
|
|
485
|
+
affinity_score: (a.affinity_score * 100).toFixed(1) + '%',
|
|
486
|
+
})));
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
console.error(colors.status.error('✗ Failed to get affinity'));
|
|
490
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
}))
|
|
494
|
+
.addCommand(new commander_1.Command('edges')
|
|
495
|
+
.description('Show ontology-to-ontology edges (OVERLAPS, SPECIALIZES, GENERALIZES). Derived by annealing cycles or created manually.')
|
|
496
|
+
.showHelpAfterError()
|
|
497
|
+
.argument('<name>', 'Ontology name')
|
|
498
|
+
.action(async (name) => {
|
|
499
|
+
try {
|
|
500
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
501
|
+
const result = await client.getOntologyEdges(name);
|
|
502
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
503
|
+
console.log(colors.ui.title(`Ontology Edges: ${name}`));
|
|
504
|
+
console.log((0, colors_1.separator)());
|
|
505
|
+
if (result.count === 0) {
|
|
506
|
+
console.log(colors.status.warning('\nNo ontology edges found'));
|
|
507
|
+
console.log('Edges are derived during annealing cycles or created manually.');
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const table = new table_1.Table({
|
|
511
|
+
columns: [
|
|
512
|
+
{ header: 'Type', field: 'edge_type', type: 'text', width: 14, align: 'left' },
|
|
513
|
+
{ header: 'Dir', field: 'direction', type: 'text', width: 10, align: 'left' },
|
|
514
|
+
{ header: 'Other Ontology', field: 'other_name', type: 'heading', width: 'flex', priority: 3 },
|
|
515
|
+
{ header: 'Score', field: 'score', type: 'text', width: 8, align: 'right' },
|
|
516
|
+
{ header: 'Shared', field: 'shared_concept_count', type: 'count', width: 8, align: 'right' },
|
|
517
|
+
{ header: 'Source', field: 'source', type: 'text', width: 16, align: 'left' },
|
|
518
|
+
]
|
|
519
|
+
});
|
|
520
|
+
table.print(result.edges.map(e => ({
|
|
521
|
+
...e,
|
|
522
|
+
other_name: e.direction === 'outgoing' ? e.to_ontology : e.from_ontology,
|
|
523
|
+
score: e.score.toFixed(3),
|
|
524
|
+
})));
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
console.error(colors.status.error('Failed to get ontology edges'));
|
|
528
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
}))
|
|
532
|
+
.addCommand(new commander_1.Command('reassign')
|
|
533
|
+
.description('Move sources from one ontology to another. Updates s.document and SCOPED_BY edges. Refuses if source ontology is frozen.')
|
|
534
|
+
.showHelpAfterError()
|
|
535
|
+
.argument('<from>', 'Source ontology name')
|
|
536
|
+
.requiredOption('--to <target>', 'Target ontology name')
|
|
537
|
+
.requiredOption('--source-ids <ids...>', 'Source IDs to move')
|
|
538
|
+
.action(async (from, options) => {
|
|
539
|
+
try {
|
|
540
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
541
|
+
const result = await client.reassignSources(from, options.to, options.sourceIds);
|
|
542
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
543
|
+
console.log(colors.status.success(`✓ Reassigned ${result.sources_reassigned} sources`));
|
|
544
|
+
console.log((0, colors_1.separator)());
|
|
545
|
+
console.log(` ${colors.stats.label('From:')} ${result.from_ontology}`);
|
|
546
|
+
console.log(` ${colors.stats.label('To:')} ${result.to_ontology}`);
|
|
547
|
+
console.log(` ${colors.stats.label('Sources moved:')} ${(0, colors_1.coloredCount)(result.sources_reassigned)}`);
|
|
548
|
+
console.log((0, colors_1.separator)());
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
console.error(colors.status.error('✗ Failed to reassign sources'));
|
|
552
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
}))
|
|
556
|
+
.addCommand(new commander_1.Command('dissolve')
|
|
557
|
+
.description('Dissolve an ontology non-destructively. Moves all sources to the target ontology, then removes the Ontology node. Unlike delete, this preserves all data. Refuses if ontology is pinned or frozen.')
|
|
558
|
+
.showHelpAfterError()
|
|
559
|
+
.argument('<name>', 'Ontology to dissolve')
|
|
560
|
+
.requiredOption('--into <target>', 'Target ontology to receive sources')
|
|
561
|
+
.action(async (name, options) => {
|
|
562
|
+
try {
|
|
563
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
564
|
+
const result = await client.dissolveOntology(name, options.into);
|
|
565
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
566
|
+
console.log(colors.status.success(`✓ Dissolved ontology "${name}"`));
|
|
567
|
+
console.log((0, colors_1.separator)());
|
|
568
|
+
console.log(` ${colors.stats.label('Sources reassigned:')} ${(0, colors_1.coloredCount)(result.sources_reassigned)}`);
|
|
569
|
+
console.log(` ${colors.stats.label('Node deleted:')} ${result.ontology_node_deleted ? colors.status.success('yes') : colors.status.dim('no')}`);
|
|
570
|
+
if (result.reassignment_targets.length > 0) {
|
|
571
|
+
console.log(` ${colors.stats.label('Targets:')} ${result.reassignment_targets.join(', ')}`);
|
|
572
|
+
}
|
|
573
|
+
console.log((0, colors_1.separator)());
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
console.error(colors.status.error('✗ Failed to dissolve ontology'));
|
|
577
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
}))
|
|
581
|
+
.addCommand(new commander_1.Command('proposals')
|
|
582
|
+
.description('List annealing proposals generated by the annealing cycle. Proposals are promotion or demotion suggestions for ontologies that require human review before execution.')
|
|
583
|
+
.option('--status <status>', 'Filter by status: pending, approved, rejected, expired')
|
|
584
|
+
.option('--type <type>', 'Filter by type: promotion, demotion')
|
|
585
|
+
.option('--ontology <name>', 'Filter by ontology name')
|
|
586
|
+
.action(async (options) => {
|
|
587
|
+
try {
|
|
588
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
589
|
+
const result = await client.listProposals({
|
|
590
|
+
status: options.status,
|
|
591
|
+
proposal_type: options.type,
|
|
592
|
+
ontology: options.ontology,
|
|
593
|
+
});
|
|
594
|
+
if (result.count === 0) {
|
|
595
|
+
console.log(colors.status.warning('\n⚠ No proposals found'));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
console.log('\n' + colors.ui.title('📋 Annealing Proposals'));
|
|
599
|
+
console.log((0, colors_1.separator)());
|
|
600
|
+
for (const p of result.proposals) {
|
|
601
|
+
const typeIcon = p.proposal_type === 'promotion' ? '⬆' : '⬇';
|
|
602
|
+
const statusColor = p.status === 'pending' ? colors.status.warning
|
|
603
|
+
: p.status === 'approved' ? colors.status.success
|
|
604
|
+
: p.status === 'executed' ? colors.status.success
|
|
605
|
+
: p.status === 'rejected' ? colors.status.error
|
|
606
|
+
: p.status === 'failed' ? colors.status.error
|
|
607
|
+
: colors.status.dim;
|
|
608
|
+
console.log(` ${colors.ui.key(`#${p.id}`)} ${typeIcon} ${colors.evidence.document(p.ontology_name)} ${statusColor(`[${p.status}]`)}`);
|
|
609
|
+
console.log(` ${colors.status.dim(p.reasoning.substring(0, 120))}${p.reasoning.length > 120 ? '...' : ''}`);
|
|
610
|
+
if (p.proposal_type === 'promotion' && p.anchor_concept_id) {
|
|
611
|
+
console.log(` ${colors.stats.label('Anchor:')} ${p.anchor_concept_id}`);
|
|
612
|
+
}
|
|
613
|
+
if (p.proposal_type === 'demotion' && p.target_ontology) {
|
|
614
|
+
console.log(` ${colors.stats.label('Target:')} ${p.target_ontology}`);
|
|
615
|
+
}
|
|
616
|
+
if (p.protection_score !== null && p.protection_score !== undefined) {
|
|
617
|
+
console.log(` ${colors.stats.label('Scores:')} mass=${p.mass_score?.toFixed(3)} coherence=${p.coherence_score?.toFixed(3)} protection=${p.protection_score?.toFixed(3)}`);
|
|
618
|
+
}
|
|
619
|
+
console.log(` ${colors.stats.label('Epoch:')} ${p.created_at_epoch} ${colors.stats.label('Created:')} ${new Date(p.created_at).toLocaleString()}`);
|
|
620
|
+
console.log();
|
|
621
|
+
}
|
|
622
|
+
console.log((0, colors_1.separator)());
|
|
623
|
+
console.log(` ${(0, colors_1.coloredCount)(result.count)} proposal${result.count !== 1 ? 's' : ''}`);
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
console.error(colors.status.error('✗ Failed to list proposals'));
|
|
627
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
}))
|
|
631
|
+
.addCommand(new commander_1.Command('proposal')
|
|
632
|
+
.description('View or review a specific annealing proposal.')
|
|
633
|
+
.argument('<id>', 'Proposal ID')
|
|
634
|
+
.option('--approve', 'Approve this proposal')
|
|
635
|
+
.option('--reject', 'Reject this proposal')
|
|
636
|
+
.option('--notes <notes>', 'Review notes')
|
|
637
|
+
.action(async (id, options) => {
|
|
638
|
+
try {
|
|
639
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
640
|
+
const proposalId = parseInt(id, 10);
|
|
641
|
+
if (options.approve || options.reject) {
|
|
642
|
+
const status = options.approve ? 'approved' : 'rejected';
|
|
643
|
+
const result = await client.reviewProposal(proposalId, status, options.notes);
|
|
644
|
+
console.log('\n' + (0, colors_1.separator)());
|
|
645
|
+
console.log(colors.status.success(`✓ Proposal #${proposalId} ${status}`));
|
|
646
|
+
if (options.notes) {
|
|
647
|
+
console.log(` ${colors.stats.label('Notes:')} ${options.notes}`);
|
|
648
|
+
}
|
|
649
|
+
console.log((0, colors_1.separator)());
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
// View proposal details
|
|
653
|
+
const p = await client.getProposal(proposalId);
|
|
654
|
+
const typeIcon = p.proposal_type === 'promotion' ? '⬆ Promotion' : '⬇ Demotion';
|
|
655
|
+
const statusColor = p.status === 'pending' ? colors.status.warning
|
|
656
|
+
: p.status === 'approved' ? colors.status.success
|
|
657
|
+
: p.status === 'executed' ? colors.status.success
|
|
658
|
+
: p.status === 'rejected' ? colors.status.error
|
|
659
|
+
: p.status === 'failed' ? colors.status.error
|
|
660
|
+
: colors.status.dim;
|
|
661
|
+
console.log('\n' + colors.ui.title(`📋 Proposal #${p.id}: ${typeIcon}`));
|
|
662
|
+
console.log((0, colors_1.separator)());
|
|
663
|
+
console.log(` ${colors.stats.label('Ontology:')} ${colors.evidence.document(p.ontology_name)}`);
|
|
664
|
+
console.log(` ${colors.stats.label('Status:')} ${statusColor(p.status)}`);
|
|
665
|
+
console.log(` ${colors.stats.label('Reasoning:')} ${p.reasoning}`);
|
|
666
|
+
if (p.anchor_concept_id) {
|
|
667
|
+
console.log(` ${colors.stats.label('Anchor Concept:')} ${p.anchor_concept_id}`);
|
|
668
|
+
}
|
|
669
|
+
if (p.target_ontology) {
|
|
670
|
+
console.log(` ${colors.stats.label('Absorption Target:')} ${p.target_ontology}`);
|
|
671
|
+
}
|
|
672
|
+
if (p.mass_score !== null && p.mass_score !== undefined) {
|
|
673
|
+
console.log(` ${colors.stats.label('Mass:')} ${p.mass_score.toFixed(4)}`);
|
|
674
|
+
}
|
|
675
|
+
if (p.coherence_score !== null && p.coherence_score !== undefined) {
|
|
676
|
+
console.log(` ${colors.stats.label('Coherence:')} ${p.coherence_score.toFixed(4)}`);
|
|
677
|
+
}
|
|
678
|
+
if (p.protection_score !== null && p.protection_score !== undefined) {
|
|
679
|
+
console.log(` ${colors.stats.label('Protection:')} ${p.protection_score.toFixed(4)}`);
|
|
680
|
+
}
|
|
681
|
+
console.log(` ${colors.stats.label('Epoch:')} ${p.created_at_epoch}`);
|
|
682
|
+
console.log(` ${colors.stats.label('Created:')} ${new Date(p.created_at).toLocaleString()}`);
|
|
683
|
+
if (p.reviewed_at) {
|
|
684
|
+
console.log(` ${colors.stats.label('Reviewed:')} ${new Date(p.reviewed_at).toLocaleString()} by ${p.reviewed_by}`);
|
|
685
|
+
}
|
|
686
|
+
if (p.reviewer_notes) {
|
|
687
|
+
console.log(` ${colors.stats.label('Notes:')} ${p.reviewer_notes}`);
|
|
688
|
+
}
|
|
689
|
+
if (p.suggested_name) {
|
|
690
|
+
console.log(` ${colors.stats.label('Suggested Name:')} ${p.suggested_name}`);
|
|
691
|
+
}
|
|
692
|
+
if (p.suggested_description) {
|
|
693
|
+
console.log(` ${colors.stats.label('Suggested Description:')} ${p.suggested_description}`);
|
|
694
|
+
}
|
|
695
|
+
// Phase 4: execution details
|
|
696
|
+
if (p.executed_at) {
|
|
697
|
+
console.log(` ${colors.stats.label('Executed:')} ${new Date(p.executed_at).toLocaleString()}`);
|
|
698
|
+
}
|
|
699
|
+
if (p.execution_result) {
|
|
700
|
+
const r = p.execution_result;
|
|
701
|
+
if (r.ontology_created) {
|
|
702
|
+
console.log(` ${colors.stats.label('Created Ontology:')} ${colors.evidence.document(String(r.ontology_created))}`);
|
|
703
|
+
}
|
|
704
|
+
if (r.absorbed_into) {
|
|
705
|
+
console.log(` ${colors.stats.label('Absorbed Into:')} ${colors.evidence.document(String(r.absorbed_into))}`);
|
|
706
|
+
}
|
|
707
|
+
if (r.sources_reassigned !== undefined) {
|
|
708
|
+
console.log(` ${colors.stats.label('Sources Reassigned:')} ${r.sources_reassigned}`);
|
|
709
|
+
}
|
|
710
|
+
if (r.error) {
|
|
711
|
+
console.log(` ${colors.status.error('Error:')} ${r.error}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
console.log((0, colors_1.separator)());
|
|
715
|
+
if (p.status === 'pending') {
|
|
716
|
+
console.log(colors.status.dim(' Use --approve or --reject to review this proposal'));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
console.error(colors.status.error('✗ Failed to get proposal'));
|
|
721
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
}))
|
|
725
|
+
.addCommand(new commander_1.Command('anneal')
|
|
726
|
+
.description('Trigger a annealing cycle. Scores all ontologies, recomputes centroids, identifies candidates, and generates proposals for review. Use --dry-run to preview candidates without generating proposals.')
|
|
727
|
+
.option('--dry-run', 'Preview candidates without generating proposals')
|
|
728
|
+
.option('--demotion-threshold <threshold>', 'Protection score below which to consider demotion', '0.15')
|
|
729
|
+
.option('--promotion-min-degree <degree>', 'Minimum concept degree for promotion candidacy', '10')
|
|
730
|
+
.option('--max-proposals <count>', 'Maximum proposals per cycle', '5')
|
|
731
|
+
.action(async (options) => {
|
|
732
|
+
try {
|
|
733
|
+
const client = (0, client_1.createClientFromEnv)();
|
|
734
|
+
console.log('\n' + colors.ui.title('🫁 Running Annealing Cycle'));
|
|
735
|
+
if (options.dryRun) {
|
|
736
|
+
console.log(colors.status.dim(' (dry run — no proposals will be generated)'));
|
|
737
|
+
}
|
|
738
|
+
console.log();
|
|
739
|
+
const result = await client.triggerAnnealingCycle({
|
|
740
|
+
dry_run: options.dryRun || false,
|
|
741
|
+
demotion_threshold: parseFloat(options.demotionThreshold),
|
|
742
|
+
promotion_min_degree: parseInt(options.promotionMinDegree, 10),
|
|
743
|
+
max_proposals: parseInt(options.maxProposals, 10),
|
|
744
|
+
});
|
|
745
|
+
console.log((0, colors_1.separator)());
|
|
746
|
+
console.log(` ${colors.stats.label('Epoch:')} ${result.cycle_epoch}`);
|
|
747
|
+
console.log(` ${colors.stats.label('Ontologies scored:')} ${(0, colors_1.coloredCount)(result.scores_updated)}`);
|
|
748
|
+
console.log(` ${colors.stats.label('Centroids updated:')} ${(0, colors_1.coloredCount)(result.centroids_updated)}`);
|
|
749
|
+
console.log(` ${colors.stats.label('Demotion candidates:')} ${(0, colors_1.coloredCount)(result.demotion_candidates)}`);
|
|
750
|
+
console.log(` ${colors.stats.label('Promotion candidates:')} ${(0, colors_1.coloredCount)(result.promotion_candidates)}`);
|
|
751
|
+
console.log(` ${colors.stats.label('Proposals generated:')} ${(0, colors_1.coloredCount)(result.proposals_generated)}`);
|
|
752
|
+
console.log(` ${colors.stats.label('Dry run:')} ${result.dry_run ? 'yes' : 'no'}`);
|
|
753
|
+
console.log((0, colors_1.separator)());
|
|
754
|
+
if (result.proposals_generated > 0) {
|
|
755
|
+
console.log(colors.status.dim(' Use `kg ontology proposals` to review proposals'));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
console.error(colors.status.error('✗ Annealing cycle failed'));
|
|
760
|
+
console.error(colors.status.error(error.response?.data?.detail || error.message));
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
230
763
|
}));
|
|
231
764
|
//# sourceMappingURL=ontology.js.map
|