fluffy-context 0.7.6 → 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/README.md +115 -36
- package/dist/src/agent/api.d.ts +49 -4
- package/dist/src/agent/api.js +183 -131
- package/dist/src/agent/index.d.ts +7 -1
- package/dist/src/agent/index.js +1 -0
- package/dist/src/cli/main.js +301 -15
- package/dist/src/cognition/evidence-graph.d.ts +4 -0
- package/dist/src/cognition/evidence-graph.js +187 -0
- package/dist/src/cognition/evolution.d.ts +36 -0
- package/dist/src/cognition/evolution.js +221 -0
- package/dist/src/cognition/journal.js +143 -2
- package/dist/src/cognition/projections.js +124 -2
- package/dist/src/cognition/recipes.d.ts +25 -0
- package/dist/src/cognition/recipes.js +149 -0
- package/dist/src/cognition/skills.d.ts +45 -0
- package/dist/src/cognition/skills.js +184 -0
- package/dist/src/cognition/types.d.ts +196 -2
- package/dist/src/cognition/usage-report.d.ts +40 -0
- package/dist/src/cognition/usage-report.js +232 -0
- package/dist/src/cognition/usage.d.ts +5 -0
- package/dist/src/cognition/usage.js +48 -0
- package/dist/src/compiler/compile.js +73 -4
- package/dist/src/compiler/graph.d.ts +7 -0
- package/dist/src/compiler/graph.js +117 -0
- package/dist/src/compiler/identity.d.ts +1 -1
- package/dist/src/compiler/identity.js +3 -1
- package/dist/src/compiler/sources.d.ts +3 -1
- package/dist/src/compiler/sources.js +11 -2
- package/dist/src/compiler/types.d.ts +23 -2
- package/dist/src/mcp/server.js +228 -6
- package/dist/src/storage/layout.d.ts +5 -0
- package/dist/src/storage/layout.js +15 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/skills/fluffy-context/SKILL.md +53 -5
package/dist/src/cli/main.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { checkpoint
|
|
3
|
-
import { contextOrient } from '../agent/api.js';
|
|
4
|
-
import {
|
|
5
|
-
import { contextExpand } from '../agent/api.js';
|
|
2
|
+
import { checkpoint } from '../runtime/runtime.js';
|
|
3
|
+
import { contextCompile, contextExpand, contextOrient, contextUsageReport, inspectRecipe, inspectSkill, listRecipes, listSkills, loadContext, materializeContextRecipe, materializeContextSkill, recordContextSkillInvocation, recordContextSkillOutcome, transitionContextRecipe, transitionContextSkill, acceptContextEvolutionCandidate, inspectContextEvolutionProposal, listContextEvolutionProposals, proposeContextEvolution, transitionContextEvolutionProposal } from '../agent/api.js';
|
|
4
|
+
import { formatUsageReport } from '../cognition/usage-report.js';
|
|
6
5
|
import { importV1, verifyV1Import } from '../cognition/migration-v1.js';
|
|
7
6
|
import { recordUse } from '../cognition/feedback.js';
|
|
8
7
|
import { verifyJournal } from '../cognition/journal.js';
|
|
@@ -75,6 +74,16 @@ function enumOption(args, name, values) {
|
|
|
75
74
|
throw new Error(`${name} must be one of: ${values.join(', ')}`);
|
|
76
75
|
return value;
|
|
77
76
|
}
|
|
77
|
+
function graphHopsOption(args) {
|
|
78
|
+
const value = option(args, '--graph-hops');
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (value !== '0' && value !== '1' && value !== '2')
|
|
82
|
+
throw new Error('--graph-hops must be one of: 0, 1, 2');
|
|
83
|
+
return Number(value);
|
|
84
|
+
}
|
|
85
|
+
const recipeBudgets = (value) => value === undefined ? undefined : Object.fromEntries(value.split(',').map((item) => { const [section, budget] = item.split('=', 2); const parsed = Number(budget); if (!section || !Number.isSafeInteger(parsed) || parsed < 0 || parsed > 100_000)
|
|
86
|
+
throw new Error('--budgets must be section=non-negative-integer up to 100000'); return [section, parsed]; }));
|
|
78
87
|
const HELP = {
|
|
79
88
|
init: `usage: ctx init [path] [--path <path>]
|
|
80
89
|
|
|
@@ -82,6 +91,34 @@ Initialize the local .context runtime layout. Re-running init is safe.
|
|
|
82
91
|
|
|
83
92
|
Options:
|
|
84
93
|
--path <path> Project path`,
|
|
94
|
+
usage: `usage: ctx usage report [options]
|
|
95
|
+
|
|
96
|
+
Show read-only context asset, reuse, token investment, and operation metrics.
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
--path <path> Project path
|
|
100
|
+
--since <ISO> Window start
|
|
101
|
+
--until <ISO> Window end
|
|
102
|
+
--days <number> Rolling window length (default: 30)
|
|
103
|
+
--month <YYYY-MM> Calendar month window
|
|
104
|
+
--granularity <value> day|week|month
|
|
105
|
+
--format <value> json|ascii (default: json)
|
|
106
|
+
--baseline-tokens <number> Explicit comparison token count
|
|
107
|
+
--baseline-source <value> manual|recorded-control`,
|
|
108
|
+
'usage report': `usage: ctx usage report [options]
|
|
109
|
+
|
|
110
|
+
Show read-only context asset, reuse, token investment, and operation metrics.
|
|
111
|
+
|
|
112
|
+
Options:
|
|
113
|
+
--path <path> Project path
|
|
114
|
+
--since <ISO> Window start
|
|
115
|
+
--until <ISO> Window end
|
|
116
|
+
--days <number> Rolling window length (default: 30)
|
|
117
|
+
--month <YYYY-MM> Calendar month window
|
|
118
|
+
--granularity <value> day|week|month
|
|
119
|
+
--format <value> json|ascii (default: json)
|
|
120
|
+
--baseline-tokens <number> Explicit comparison token count
|
|
121
|
+
--baseline-source <value> manual|recorded-control`,
|
|
85
122
|
checkpoint: `usage: ctx checkpoint [options]
|
|
86
123
|
|
|
87
124
|
Save structured work state as a baseline or incremental snapshot.
|
|
@@ -125,6 +162,8 @@ Compile bounded, provider-neutral context with source provenance and selection e
|
|
|
125
162
|
Options:
|
|
126
163
|
--path <path> Project path
|
|
127
164
|
--context <id> Context ID
|
|
165
|
+
--recipe-id <id> Explicit verified or published Recipe
|
|
166
|
+
--graph-hops <0|1|2> Optional bounded evidence-graph traversal
|
|
128
167
|
--scenario <scenario> resume|implementation|debugging|verification|handoff|exploration|unknown
|
|
129
168
|
--scope <scope> Exact consensus scope
|
|
130
169
|
--paths <paths> Comma-separated project-relative paths
|
|
@@ -146,6 +185,8 @@ Options:
|
|
|
146
185
|
--max-chars <number> Maximum expanded characters
|
|
147
186
|
--compile-max-chars <number> Original compile budget (default: 4000)
|
|
148
187
|
--context <id> Context ID
|
|
188
|
+
--recipe-id <id> Explicit verified or published Recipe
|
|
189
|
+
--graph-hops <0|1|2> Optional bounded evidence-graph traversal
|
|
149
190
|
--query <text> Original compile query
|
|
150
191
|
--scenario <scenario> resume|implementation|debugging|verification|handoff|exploration|unknown
|
|
151
192
|
--scope <scope> Exact consensus scope
|
|
@@ -161,7 +202,7 @@ Start an MCP stdio server for Agent integrations.
|
|
|
161
202
|
Run "ctx agent serve --help" for details.`,
|
|
162
203
|
'agent serve': `usage: ctx agent serve
|
|
163
204
|
|
|
164
|
-
Start an MCP stdio server exposing context_orient, context_expand, context_compile, context_resume, context_note_list, context_note_add, and context_checkpoint tools.
|
|
205
|
+
Start an MCP stdio server exposing context_orient, context_expand, context_compile, context_resume, context_note_list, context_note_add, context_usage_report, and context_checkpoint tools.
|
|
165
206
|
|
|
166
207
|
The server owns standard input/output; do not use it interactively.`,
|
|
167
208
|
hook: `usage: ctx hook claude-code session-start|user-prompt
|
|
@@ -259,6 +300,13 @@ Record explicit use of verified v2 retrieval material.`,
|
|
|
259
300
|
'feedback use': `usage: ctx feedback use knowledge|deadend <id> --event-id <stable-id> [--path <path>]
|
|
260
301
|
|
|
261
302
|
Record an idempotent use signal that affects deterministic rank only.`,
|
|
303
|
+
skill: `usage: ctx skill list [--path <path>]
|
|
304
|
+
ctx skill inspect <skill-id> [--path <path>]
|
|
305
|
+
ctx skill materialize <proposal-id> --title <title> --procedure <steps> [--path <path>]
|
|
306
|
+
ctx skill verify|publish|deprecate <skill-id> --rationale <text> [--path <path>]
|
|
307
|
+
ctx skill supersede <skill-id> --rationale <text> --supersedes <entity:id> [--path <path>]
|
|
308
|
+
ctx skill invoke <skill-id> --invocation-id <id> --caller-event <id> --input-hash <hash> [--path <path>]
|
|
309
|
+
ctx skill outcome <skill-id> --invocation-id <id> --outcome-id <id> --caller-event <id> --outcome <value> --summary <text> [--path <path>]`,
|
|
262
310
|
journal: `usage: ctx journal verify [--path <path>]
|
|
263
311
|
|
|
264
312
|
Validate the v2 journal hash chain and replay projections in memory.`,
|
|
@@ -325,9 +373,49 @@ Rebuild v2 projections and verify imported v1 parity.
|
|
|
325
373
|
|
|
326
374
|
Options:
|
|
327
375
|
--path <path> Project path`,
|
|
328
|
-
|
|
376
|
+
recipe: `usage: ctx recipe list|inspect|materialize|verify|publish|deprecate|supersede [options]
|
|
377
|
+
|
|
378
|
+
List, inspect, materialize, or govern immutable compiler Recipes.
|
|
379
|
+
|
|
380
|
+
Options:
|
|
381
|
+
--path <path> Project path
|
|
382
|
+
--title <text> Recipe title for materialize
|
|
383
|
+
--sources <items> Comma-separated source preferences
|
|
384
|
+
--sections <items> Comma-separated section preferences
|
|
385
|
+
--budgets <items> Comma-separated section=characters budgets
|
|
386
|
+
--rationale <text> Governance rationale
|
|
387
|
+
--supersedes <ref> Superseding entity:id`,
|
|
388
|
+
'recipe verify': `usage: ctx recipe verify <recipe-id> --rationale <text> [--path <path>]
|
|
389
|
+
|
|
390
|
+
Mark a candidate compiler Recipe as verified.`,
|
|
391
|
+
'recipe publish': `usage: ctx recipe publish <recipe-id> --rationale <text> [--path <path>]
|
|
392
|
+
|
|
393
|
+
Publish a verified compiler Recipe.`,
|
|
394
|
+
'recipe deprecate': `usage: ctx recipe deprecate <recipe-id> --rationale <text> [--path <path>]
|
|
395
|
+
|
|
396
|
+
Retire a compiler Recipe with an explicit governance reason.`,
|
|
397
|
+
'recipe supersede': `usage: ctx recipe supersede <recipe-id> --rationale <text> --supersedes <entity:id> [--path <path>]
|
|
398
|
+
|
|
399
|
+
Supersede a compiler Recipe while retaining its history.`,
|
|
400
|
+
evolution: `usage: ctx evolution propose|list|inspect|accept|verify|reject|deprecate|supersede [options]
|
|
401
|
+
|
|
402
|
+
Create and govern bounded, metadata-only Evolution proposals.
|
|
329
403
|
|
|
330
|
-
|
|
404
|
+
Options:
|
|
405
|
+
--path <path> Project path
|
|
406
|
+
--events <items> Comma-separated source event IDs
|
|
407
|
+
--recent-limit <n> Number of recent source events
|
|
408
|
+
--target-kind <kind> skill|compiler-recipe
|
|
409
|
+
--rationale <text> Governance rationale
|
|
410
|
+
--supersedes <ref> Superseding entity:id`,
|
|
411
|
+
'evolution propose': `usage: ctx evolution propose [--events <event-id,...>|--recent-limit <n>] [--target-kind skill|compiler-recipe] [--path <path>]`,
|
|
412
|
+
'evolution list': `usage: ctx evolution list [--path <path>]`,
|
|
413
|
+
'evolution inspect': `usage: ctx evolution inspect <proposal-id> [--path <path>]`,
|
|
414
|
+
'evolution accept': `usage: ctx evolution accept <proposal-id> --rationale <text> [--path <path>]`,
|
|
415
|
+
'evolution verify': `usage: ctx evolution verify <proposal-id> --rationale <text> [--path <path>]`,
|
|
416
|
+
'evolution reject': `usage: ctx evolution reject <proposal-id> --rationale <text> [--path <path>]`,
|
|
417
|
+
'evolution deprecate': `usage: ctx evolution deprecate <proposal-id> --rationale <text> [--path <path>]`,
|
|
418
|
+
'evolution supersede': `usage: ctx evolution supersede <proposal-id> --rationale <text> --supersedes <entity:id> [--path <path>]`,
|
|
331
419
|
filesystem: `usage: ctx filesystem enable|disable|status [--path <path>]
|
|
332
420
|
|
|
333
421
|
Control whether the Runtime records filtered file metadata changes.`,
|
|
@@ -340,6 +428,9 @@ Disable filesystem change observation and restart a running Runtime.`,
|
|
|
340
428
|
'filesystem status': `usage: ctx filesystem status [--path <path>]
|
|
341
429
|
|
|
342
430
|
Show the filesystem observation policy and Runtime status.`,
|
|
431
|
+
runtime: `usage: ctx runtime enable|disable|start|stop|status [--path <path>]
|
|
432
|
+
|
|
433
|
+
Control the local Knowledge Runtime lifecycle.`,
|
|
343
434
|
'runtime status': `usage: ctx runtime status [--path <path>]
|
|
344
435
|
|
|
345
436
|
Show the read-only runtime policy, migration and authenticated lease status.
|
|
@@ -363,7 +454,7 @@ Stop the local runtime through authenticated loopback IPC.`,
|
|
|
363
454
|
Internal runtime daemon entry point.`,
|
|
364
455
|
};
|
|
365
456
|
function usage() {
|
|
366
|
-
return `usage: ctx init|checkpoint|resume|orient|compile|expand|agent|hook|integrate|status|doctor|learn|knowledge|deadend|deadends|note|activity|migrate|runtime|filesystem|feedback|journal [options]
|
|
457
|
+
return `usage: ctx init|checkpoint|resume|orient|compile|expand|usage|agent|hook|integrate|status|doctor|learn|knowledge|deadend|deadends|note|activity|migrate|runtime|filesystem|feedback|skill|recipe|evolution|journal [options]
|
|
367
458
|
|
|
368
459
|
Run \"ctx <command> --help\" for command details.`;
|
|
369
460
|
}
|
|
@@ -391,8 +482,10 @@ function printHelp(args) {
|
|
|
391
482
|
|| (command === 'journal' && args[1] === 'verify')
|
|
392
483
|
|| (command === 'note' && ['add', 'list'].includes(args[1]))
|
|
393
484
|
|| (command === 'migrate' && ['v1', 'verify'].includes(args[1]))
|
|
485
|
+
|| (command === 'recipe' && ['list', 'inspect', 'materialize', 'verify', 'publish', 'deprecate', 'supersede'].includes(args[1]))
|
|
486
|
+
|| (command === 'evolution' && ['propose', 'list', 'inspect', 'accept', 'verify', 'reject', 'deprecate', 'supersede'].includes(args[1]))
|
|
394
487
|
|| (command === 'runtime' && ['enable', 'disable', 'start', 'stop', 'status', 'serve'].includes(args[1]))
|
|
395
|
-
|| (command === '
|
|
488
|
+
|| (command === 'usage' && args[1] === 'report');
|
|
396
489
|
const key = nested ? `${command} ${args[1]}` : command;
|
|
397
490
|
const nestedKey = key === 'integrate claude' && (args[2] === 'inspect' || args[2] === 'install') ? `${key} ${args[2]}` : key;
|
|
398
491
|
const help = HELP[nestedKey];
|
|
@@ -450,7 +543,7 @@ async function run(args) {
|
|
|
450
543
|
case 'resume':
|
|
451
544
|
validateOptions(args.slice(1), ['--path', '--context', '--max-chars'], ['--path', '--context', '--max-chars']);
|
|
452
545
|
validatePositionals(positionals(args.slice(1), ['--path', '--context', '--max-chars']), 0, HELP.resume);
|
|
453
|
-
print(await
|
|
546
|
+
print(await loadContext(target, { contextId: option(args, '--context'), maxChars: numericOption(args, '--max-chars', 4000) }, 'cli'));
|
|
454
547
|
return;
|
|
455
548
|
case 'orient': {
|
|
456
549
|
const valueOptions = ['--path', '--context', '--scope', '--max-chars', '--knowledge-limit', '--deadend-limit', '--note-limit'];
|
|
@@ -466,16 +559,18 @@ async function run(args) {
|
|
|
466
559
|
knowledgeLimit: numericOption(args, '--knowledge-limit', 10),
|
|
467
560
|
deadendLimit: numericOption(args, '--deadend-limit', 10),
|
|
468
561
|
noteLimit: numericOption(args, '--note-limit', 20),
|
|
469
|
-
}));
|
|
562
|
+
}, 'cli'));
|
|
470
563
|
return;
|
|
471
564
|
}
|
|
472
565
|
case 'compile': {
|
|
473
|
-
const valueOptions = ['--path', '--context', '--scenario', '--scope', '--paths', '--max-chars', '--knowledge-limit', '--deadend-limit', '--note-limit', '--activity-limit'];
|
|
566
|
+
const valueOptions = ['--path', '--context', '--recipe-id', '--graph-hops', '--scenario', '--scope', '--paths', '--max-chars', '--knowledge-limit', '--deadend-limit', '--note-limit', '--activity-limit'];
|
|
474
567
|
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
475
568
|
const query = positionals(args.slice(1), valueOptions);
|
|
476
569
|
validatePositionals(query, 1, HELP.compile);
|
|
477
|
-
print(await
|
|
570
|
+
print(await contextCompile(target, {
|
|
478
571
|
contextId: option(args, '--context'),
|
|
572
|
+
recipeId: option(args, '--recipe-id'),
|
|
573
|
+
graphHops: graphHopsOption(args),
|
|
479
574
|
query: query[0],
|
|
480
575
|
scenario: enumOption(args, '--scenario', ['resume', 'implementation', 'debugging', 'verification', 'handoff', 'exploration', 'unknown']),
|
|
481
576
|
scope: option(args, '--scope'),
|
|
@@ -485,11 +580,11 @@ async function run(args) {
|
|
|
485
580
|
deadendLimit: numericOption(args, '--deadend-limit', 10),
|
|
486
581
|
noteLimit: numericOption(args, '--note-limit', 20),
|
|
487
582
|
activityLimit: numericOption(args, '--activity-limit', 20),
|
|
488
|
-
}));
|
|
583
|
+
}, 'cli'));
|
|
489
584
|
return;
|
|
490
585
|
}
|
|
491
586
|
case 'expand': {
|
|
492
|
-
const valueOptions = ['--path', '--manifest-hash', '--candidate-id', '--item-hash', '--level', '--max-chars', '--compile-max-chars', '--context', '--query', '--scenario', '--scope', '--paths', '--knowledge-limit', '--deadend-limit', '--note-limit', '--activity-limit'];
|
|
587
|
+
const valueOptions = ['--path', '--manifest-hash', '--candidate-id', '--item-hash', '--level', '--max-chars', '--compile-max-chars', '--context', '--recipe-id', '--graph-hops', '--query', '--scenario', '--scope', '--paths', '--knowledge-limit', '--deadend-limit', '--note-limit', '--activity-limit'];
|
|
493
588
|
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
494
589
|
validatePositionals(positionals(args.slice(1), valueOptions), 0, HELP.expand);
|
|
495
590
|
const level = enumOption(args, '--level', ['structured', 'evidence']);
|
|
@@ -508,6 +603,8 @@ async function run(args) {
|
|
|
508
603
|
maxChars: option(args, '--max-chars') === undefined ? undefined : numericOption(args, '--max-chars', 0),
|
|
509
604
|
compile: {
|
|
510
605
|
contextId: option(args, '--context'),
|
|
606
|
+
recipeId: option(args, '--recipe-id'),
|
|
607
|
+
graphHops: graphHopsOption(args),
|
|
511
608
|
query: option(args, '--query'),
|
|
512
609
|
scenario: enumOption(args, '--scenario', ['resume', 'implementation', 'debugging', 'verification', 'handoff', 'exploration', 'unknown']),
|
|
513
610
|
scope: option(args, '--scope'),
|
|
@@ -521,6 +618,27 @@ async function run(args) {
|
|
|
521
618
|
}));
|
|
522
619
|
return;
|
|
523
620
|
}
|
|
621
|
+
case 'usage': {
|
|
622
|
+
if (args[1] !== 'report')
|
|
623
|
+
throw new Error(HELP.usage);
|
|
624
|
+
const valueOptions = ['--path', '--since', '--until', '--days', '--month', '--granularity', '--format', '--baseline-tokens', '--baseline-source'];
|
|
625
|
+
validateOptions(args.slice(2), valueOptions, valueOptions);
|
|
626
|
+
validatePositionals(positionals(args.slice(2), valueOptions), 0, HELP['usage report']);
|
|
627
|
+
const report = await contextUsageReport(target, {
|
|
628
|
+
since: option(args, '--since'),
|
|
629
|
+
until: option(args, '--until'),
|
|
630
|
+
days: option(args, '--days') === undefined ? undefined : numericOption(args, '--days', 30),
|
|
631
|
+
month: option(args, '--month'),
|
|
632
|
+
granularity: enumOption(args, '--granularity', ['day', 'week', 'month']),
|
|
633
|
+
baselineTokens: option(args, '--baseline-tokens') === undefined ? undefined : numericOption(args, '--baseline-tokens', 0),
|
|
634
|
+
baselineSource: enumOption(args, '--baseline-source', ['manual', 'recorded-control']),
|
|
635
|
+
});
|
|
636
|
+
if (enumOption(args, '--format', ['json', 'ascii']) === 'ascii')
|
|
637
|
+
process.stdout.write(formatUsageReport(report));
|
|
638
|
+
else
|
|
639
|
+
print(report);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
524
642
|
case 'agent':
|
|
525
643
|
if (args[1] !== 'serve')
|
|
526
644
|
throw new Error(HELP.agent);
|
|
@@ -630,6 +748,174 @@ async function run(args) {
|
|
|
630
748
|
}
|
|
631
749
|
return;
|
|
632
750
|
}
|
|
751
|
+
case 'skill': {
|
|
752
|
+
const action = args[1];
|
|
753
|
+
if (action === 'list') {
|
|
754
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
755
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP.skill);
|
|
756
|
+
print(await listSkills(target));
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (!action)
|
|
760
|
+
throw new Error(HELP.skill);
|
|
761
|
+
const skillId = positionals(args.slice(2), ['--path', '--title', '--procedure', '--rationale', '--supersedes', '--invocation-id', '--caller-event', '--input-hash', '--outcome-id', '--outcome', '--summary'])[0];
|
|
762
|
+
if (action === 'inspect') {
|
|
763
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
764
|
+
const values = positionals(args.slice(2), ['--path']);
|
|
765
|
+
if (values.length !== 1)
|
|
766
|
+
throw new Error(HELP.skill);
|
|
767
|
+
print(await inspectSkill(target, values[0]));
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (action === 'materialize') {
|
|
771
|
+
const options = ['--path', '--title', '--procedure'];
|
|
772
|
+
validateOptions(args.slice(2), options, options);
|
|
773
|
+
const values = positionals(args.slice(2), options);
|
|
774
|
+
if (values.length !== 1)
|
|
775
|
+
throw new Error(HELP.skill);
|
|
776
|
+
print(await materializeContextSkill(target, { proposalId: values[0], title: option(args, '--title') ?? '', procedure: listOption(args, '--procedure') ?? [] }));
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (['verify', 'publish', 'deprecate', 'supersede'].includes(action)) {
|
|
780
|
+
const options = ['--path', '--rationale', '--supersedes'];
|
|
781
|
+
validateOptions(args.slice(2), options, options);
|
|
782
|
+
const values = positionals(args.slice(2), options);
|
|
783
|
+
if (values.length !== 1)
|
|
784
|
+
throw new Error(HELP.skill);
|
|
785
|
+
const supersedes = option(args, '--supersedes');
|
|
786
|
+
const [entity, entityId] = supersedes?.split(':', 2) ?? [];
|
|
787
|
+
if (action === 'supersede' && (!entity || !entityId || !['knowledge', 'deadend', 'skill', 'compiler-recipe', 'experience', 'proposal'].includes(entity)))
|
|
788
|
+
throw new Error('--supersedes must be <entity:id>');
|
|
789
|
+
print(await transitionContextSkill(target, {
|
|
790
|
+
skillId: values[0],
|
|
791
|
+
to: action === 'supersede' ? 'superseded' : action,
|
|
792
|
+
rationale: option(args, '--rationale') ?? '',
|
|
793
|
+
...(supersedes ? { supersedes: { entity: entity, entityId: entityId, relation: 'supersedes' } } : {}),
|
|
794
|
+
}));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (action === 'invoke') {
|
|
798
|
+
const options = ['--path', '--invocation-id', '--caller-event', '--input-hash'];
|
|
799
|
+
validateOptions(args.slice(2), options, options);
|
|
800
|
+
const values = positionals(args.slice(2), options);
|
|
801
|
+
if (values.length !== 1)
|
|
802
|
+
throw new Error(HELP.skill);
|
|
803
|
+
print(await recordContextSkillInvocation(target, { skillId: values[0], invocationId: option(args, '--invocation-id') ?? '', callerEventId: option(args, '--caller-event') ?? '', inputHash: option(args, '--input-hash') ?? '' }));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (action === 'outcome') {
|
|
807
|
+
const options = ['--path', '--invocation-id', '--outcome-id', '--caller-event', '--outcome', '--summary'];
|
|
808
|
+
validateOptions(args.slice(2), options, options);
|
|
809
|
+
const values = positionals(args.slice(2), options);
|
|
810
|
+
if (values.length !== 1)
|
|
811
|
+
throw new Error(HELP.skill);
|
|
812
|
+
print(await recordContextSkillOutcome(target, { skillId: values[0], invocationId: option(args, '--invocation-id') ?? '', outcomeId: option(args, '--outcome-id') ?? '', callerEventId: option(args, '--caller-event') ?? '', outcome: enumOption(args, '--outcome', ['success', 'failure', 'partial', 'irrelevant', 'unknown']) ?? 'unknown', summary: option(args, '--summary') ?? '' }));
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
void skillId;
|
|
816
|
+
throw new Error(HELP.skill);
|
|
817
|
+
}
|
|
818
|
+
case 'evolution': {
|
|
819
|
+
const action = args[1];
|
|
820
|
+
const valueOptions = ['--path', '--events', '--recent-limit', '--target-kind', '--rationale', '--supersedes'];
|
|
821
|
+
if (action === 'propose') {
|
|
822
|
+
validateOptions(args.slice(2), valueOptions.slice(0, 4), valueOptions.slice(0, 4));
|
|
823
|
+
validatePositionals(positionals(args.slice(2), valueOptions.slice(0, 4)), 0, HELP['evolution propose']);
|
|
824
|
+
if (option(args, '--events') !== undefined && option(args, '--recent-limit') !== undefined)
|
|
825
|
+
throw new Error('use either --events or --recent-limit');
|
|
826
|
+
const eventIds = listOption(args, '--events');
|
|
827
|
+
if (eventIds && (eventIds.length === 0 || eventIds.length > 32))
|
|
828
|
+
throw new Error('--events must contain 1 to 32 event IDs');
|
|
829
|
+
print(await proposeContextEvolution(target, { eventIds, recentLimit: option(args, '--recent-limit') === undefined ? undefined : numericOption(args, '--recent-limit', 32), targetKind: enumOption(args, '--target-kind', ['skill', 'compiler-recipe']) }));
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (action === 'list') {
|
|
833
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
834
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP['evolution list']);
|
|
835
|
+
print(await listContextEvolutionProposals(target));
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const values = positionals(args.slice(2), valueOptions);
|
|
839
|
+
if (action === 'inspect') {
|
|
840
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
841
|
+
if (values.length !== 1)
|
|
842
|
+
throw new Error(HELP['evolution inspect']);
|
|
843
|
+
print(await inspectContextEvolutionProposal(target, values[0]));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (['accept', 'verify', 'reject', 'deprecate', 'supersede'].includes(action ?? '')) {
|
|
847
|
+
validateOptions(args.slice(2), valueOptions, valueOptions);
|
|
848
|
+
if (values.length !== 1)
|
|
849
|
+
throw new Error(HELP[`evolution ${action}`]);
|
|
850
|
+
const rationale = option(args, '--rationale') ?? '';
|
|
851
|
+
if (!rationale.trim())
|
|
852
|
+
throw new Error('--rationale is required');
|
|
853
|
+
const supersedes = option(args, '--supersedes');
|
|
854
|
+
const [entity, entityId] = supersedes?.split(':', 2) ?? [];
|
|
855
|
+
if (action === 'supersede' && (!entity || !entityId || !['knowledge', 'deadend', 'skill', 'compiler-recipe', 'experience', 'proposal'].includes(entity)))
|
|
856
|
+
throw new Error('--supersedes must be <entity:id>');
|
|
857
|
+
if (action === 'accept')
|
|
858
|
+
print(await acceptContextEvolutionCandidate(target, { proposalId: values[0], rationale }));
|
|
859
|
+
else {
|
|
860
|
+
const to = action === 'verify' ? 'verified'
|
|
861
|
+
: action === 'supersede' ? 'superseded'
|
|
862
|
+
: action;
|
|
863
|
+
print(await transitionContextEvolutionProposal(target, { proposalId: values[0], to, rationale, ...(supersedes ? { supersedes: { entity: entity, entityId: entityId, relation: 'supersedes' } } : {}) }));
|
|
864
|
+
}
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
throw new Error(HELP.evolution);
|
|
868
|
+
}
|
|
869
|
+
case 'recipe': {
|
|
870
|
+
const action = args[1];
|
|
871
|
+
const valueOptions = ['--path', '--title', '--sources', '--sections', '--budgets', '--rationale', '--supersedes'];
|
|
872
|
+
if (action === 'list') {
|
|
873
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
874
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP.recipe);
|
|
875
|
+
print(await listRecipes(target));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (action === 'inspect') {
|
|
879
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
880
|
+
const values = positionals(args.slice(2), ['--path']);
|
|
881
|
+
if (values.length !== 1)
|
|
882
|
+
throw new Error(HELP.recipe);
|
|
883
|
+
print(await inspectRecipe(target, values[0]));
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (action === 'materialize') {
|
|
887
|
+
validateOptions(args.slice(2), valueOptions, valueOptions);
|
|
888
|
+
const values = positionals(args.slice(2), valueOptions);
|
|
889
|
+
if (values.length !== 1)
|
|
890
|
+
throw new Error(HELP.recipe);
|
|
891
|
+
print(await materializeContextRecipe(target, {
|
|
892
|
+
proposalId: values[0],
|
|
893
|
+
title: option(args, '--title') ?? '',
|
|
894
|
+
preferredSources: listOption(args, '--sources'),
|
|
895
|
+
preferredSections: listOption(args, '--sections'),
|
|
896
|
+
sectionBudgets: recipeBudgets(option(args, '--budgets')),
|
|
897
|
+
}));
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (['verify', 'publish', 'deprecate', 'supersede'].includes(action ?? '')) {
|
|
901
|
+
validateOptions(args.slice(2), ['--path', '--rationale', '--supersedes'], ['--path', '--rationale', '--supersedes']);
|
|
902
|
+
const values = positionals(args.slice(2), ['--path', '--rationale', '--supersedes']);
|
|
903
|
+
if (values.length !== 1)
|
|
904
|
+
throw new Error(HELP.recipe);
|
|
905
|
+
const supersedes = option(args, '--supersedes');
|
|
906
|
+
const [entity, entityId] = supersedes?.split(':', 2) ?? [];
|
|
907
|
+
if (action === 'supersede' && (!entity || !entityId || !['knowledge', 'deadend', 'skill', 'compiler-recipe', 'experience', 'proposal'].includes(entity)))
|
|
908
|
+
throw new Error('--supersedes must be <entity:id>');
|
|
909
|
+
print(await transitionContextRecipe(target, {
|
|
910
|
+
recipeId: values[0],
|
|
911
|
+
to: action === 'supersede' ? 'superseded' : action,
|
|
912
|
+
rationale: option(args, '--rationale') ?? '',
|
|
913
|
+
...(supersedes ? { supersedes: { entity: entity, entityId: entityId, relation: 'supersedes' } } : {}),
|
|
914
|
+
}));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
throw new Error(HELP.recipe);
|
|
918
|
+
}
|
|
633
919
|
case 'journal': {
|
|
634
920
|
if (args[1] !== 'verify')
|
|
635
921
|
throw new Error(HELP.journal);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CognitionEvent, CognitionGitAnchor, EvidenceGraphProjection } from './types.js';
|
|
2
|
+
export declare const EVIDENCE_GRAPH_DERIVATION_VERSION: "evidence-graph-v1";
|
|
3
|
+
export declare function workspaceNodeId(anchor: Pick<CognitionGitAnchor, 'worktreeId' | 'branch' | 'ref'>): string;
|
|
4
|
+
export declare function buildEvidenceGraph(events: CognitionEvent[]): EvidenceGraphProjection;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
export const EVIDENCE_GRAPH_DERIVATION_VERSION = 'evidence-graph-v1';
|
|
3
|
+
function hash(value) {
|
|
4
|
+
return crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
5
|
+
}
|
|
6
|
+
function record(event) {
|
|
7
|
+
const value = event.payload.record;
|
|
8
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
9
|
+
}
|
|
10
|
+
function normalizePath(value) {
|
|
11
|
+
const normalized = value.replaceAll('\\', '/');
|
|
12
|
+
return normalized.length > 0 && !normalized.startsWith('/') && !normalized.includes('\0') && !normalized.split('/').includes('..') ? normalized : null;
|
|
13
|
+
}
|
|
14
|
+
function entityKind(entity) {
|
|
15
|
+
if (entity === 'compiler-recipe')
|
|
16
|
+
return 'recipe';
|
|
17
|
+
if (entity === 'knowledge' || entity === 'deadend' || entity === 'skill' || entity === 'experience' || entity === 'proposal')
|
|
18
|
+
return entity;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function entityNodeId(entity, entityId) {
|
|
22
|
+
const kind = entityKind(entity);
|
|
23
|
+
return kind && entityId.trim().length > 0 ? `${kind}:${entityId}` : null;
|
|
24
|
+
}
|
|
25
|
+
export function workspaceNodeId(anchor) {
|
|
26
|
+
return `branch-worktree:${hash({ worktreeId: anchor.worktreeId, branch: anchor.branch, ref: anchor.ref }).slice(0, 16)}`;
|
|
27
|
+
}
|
|
28
|
+
function addNode(nodes, nodeId, kind) {
|
|
29
|
+
nodes.set(nodeId, { nodeId, kind });
|
|
30
|
+
}
|
|
31
|
+
function addEntityNode(nodes, entity, entityId) {
|
|
32
|
+
const nodeId = entityNodeId(entity, entityId);
|
|
33
|
+
const kind = entityKind(entity);
|
|
34
|
+
if (nodeId && kind)
|
|
35
|
+
addNode(nodes, nodeId, kind);
|
|
36
|
+
return nodeId;
|
|
37
|
+
}
|
|
38
|
+
function addEdge(edges, input) {
|
|
39
|
+
const sourceEventIds = [...new Set(input.sourceEventIds)].sort();
|
|
40
|
+
const basis = { from: input.from, to: input.to, type: input.type, sourceEventIds, occurredAt: input.occurredAt, confidence: input.confidence };
|
|
41
|
+
const edgeId = `edge:${hash(basis).slice(0, 24)}`;
|
|
42
|
+
edges.set(edgeId, { ...input, edgeId, sourceEventIds, derivationVersion: EVIDENCE_GRAPH_DERIVATION_VERSION });
|
|
43
|
+
}
|
|
44
|
+
function anchorNodes(event, nodes) {
|
|
45
|
+
const workspace = workspaceNodeId(event.git);
|
|
46
|
+
addNode(nodes, workspace, 'branch-worktree');
|
|
47
|
+
if (event.git.head)
|
|
48
|
+
addNode(nodes, `commit:${event.git.head}`, 'commit');
|
|
49
|
+
return workspace;
|
|
50
|
+
}
|
|
51
|
+
function lineageEdge(edges, nodes, from, lineage, event) {
|
|
52
|
+
const target = addEntityNode(nodes, lineage.entity, lineage.entityId);
|
|
53
|
+
if (!target)
|
|
54
|
+
return;
|
|
55
|
+
addEdge(edges, { from, to: target, type: lineage.relation, sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
56
|
+
}
|
|
57
|
+
function sourceLinks(edges, nodes, from, contextIds, snapshotIds, event) {
|
|
58
|
+
if (Array.isArray(contextIds))
|
|
59
|
+
for (const contextId of contextIds)
|
|
60
|
+
if (typeof contextId === 'string' && contextId.trim()) {
|
|
61
|
+
const target = `context:${contextId}`;
|
|
62
|
+
addNode(nodes, target, 'context');
|
|
63
|
+
addEdge(edges, { from, to: target, type: 'derived_from', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(snapshotIds))
|
|
66
|
+
for (const snapshotId of snapshotIds)
|
|
67
|
+
if (typeof snapshotId === 'string' && snapshotId.trim()) {
|
|
68
|
+
const target = `snapshot:${snapshotId}`;
|
|
69
|
+
addNode(nodes, target, 'snapshot');
|
|
70
|
+
addEdge(edges, { from, to: target, type: 'derived_from', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function buildEvidenceGraph(events) {
|
|
74
|
+
const nodes = new Map();
|
|
75
|
+
const edges = new Map();
|
|
76
|
+
for (const event of events) {
|
|
77
|
+
const workspace = anchorNodes(event, nodes);
|
|
78
|
+
if (event.git.head)
|
|
79
|
+
addEdge(edges, { from: workspace, to: `commit:${event.git.head}`, type: 'changed_in', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
80
|
+
const value = record(event);
|
|
81
|
+
if (event.type === 'context.checkpoint.imported' || event.type === 'context.state.recorded') {
|
|
82
|
+
const snapshot = value?.snapshot;
|
|
83
|
+
if (snapshot && typeof snapshot.contextId === 'string' && typeof snapshot.snapshotId === 'string') {
|
|
84
|
+
const context = `context:${snapshot.contextId}`;
|
|
85
|
+
const snap = `snapshot:${snapshot.snapshotId}`;
|
|
86
|
+
addNode(nodes, context, 'context');
|
|
87
|
+
addNode(nodes, snap, 'snapshot');
|
|
88
|
+
addEdge(edges, { from: context, to: snap, type: 'derived_from', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (event.type === 'experience.recorded' && typeof value?.experienceId === 'string') {
|
|
92
|
+
const id = addEntityNode(nodes, 'experience', value.experienceId);
|
|
93
|
+
sourceLinks(edges, nodes, id, value.sourceContextIds, value.sourceSnapshotIds, event);
|
|
94
|
+
if (Array.isArray(value.paths))
|
|
95
|
+
for (const item of value.paths)
|
|
96
|
+
if (typeof item === 'string') {
|
|
97
|
+
const path = normalizePath(item);
|
|
98
|
+
if (!path)
|
|
99
|
+
continue;
|
|
100
|
+
const target = `path:${path}`;
|
|
101
|
+
addNode(nodes, target, 'path');
|
|
102
|
+
addEdge(edges, { from: id, to: target, type: 'observed_in', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (event.type === 'evolution.proposed' && typeof value?.proposalId === 'string') {
|
|
106
|
+
const id = addEntityNode(nodes, 'proposal', value.proposalId);
|
|
107
|
+
if (Array.isArray(value.sourceExperienceIds))
|
|
108
|
+
for (const source of value.sourceExperienceIds)
|
|
109
|
+
if (typeof source === 'string') {
|
|
110
|
+
const target = addEntityNode(nodes, 'experience', source);
|
|
111
|
+
if (target)
|
|
112
|
+
addEdge(edges, { from: id, to: target, type: 'derived_from', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value.lineage))
|
|
115
|
+
for (const item of value.lineage)
|
|
116
|
+
if (item && typeof item === 'object')
|
|
117
|
+
lineageEdge(edges, nodes, id, item, event);
|
|
118
|
+
}
|
|
119
|
+
if (['knowledge.proposed', 'knowledge.verified', 'knowledge.deprecated', 'knowledge.rejected'].includes(event.type) && typeof value?.knowledgeId === 'string') {
|
|
120
|
+
const id = addEntityNode(nodes, 'knowledge', value.knowledgeId);
|
|
121
|
+
sourceLinks(edges, nodes, id, value.sourceContextIds, value.sourceSnapshotIds, event);
|
|
122
|
+
}
|
|
123
|
+
if (['deadend.proposed', 'deadend.verified', 'deadend.obsoleted', 'deadend.rejected'].includes(event.type) && typeof value?.deadendId === 'string') {
|
|
124
|
+
const id = addEntityNode(nodes, 'deadend', value.deadendId);
|
|
125
|
+
sourceLinks(edges, nodes, id, value.sourceContextId === null ? [] : [value.sourceContextId], value.sourceSnapshotId === null ? [] : [value.sourceSnapshotId], event);
|
|
126
|
+
}
|
|
127
|
+
if (event.type === 'skill.materialized' && typeof value?.skillId === 'string') {
|
|
128
|
+
const id = addEntityNode(nodes, 'skill', value.skillId);
|
|
129
|
+
const proposal = typeof value.proposalId === 'string' ? addEntityNode(nodes, 'proposal', value.proposalId) : null;
|
|
130
|
+
if (proposal)
|
|
131
|
+
addEdge(edges, { from: id, to: proposal, type: 'derived_from', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
132
|
+
if (Array.isArray(value.lineage))
|
|
133
|
+
for (const item of value.lineage)
|
|
134
|
+
if (item && typeof item === 'object')
|
|
135
|
+
lineageEdge(edges, nodes, id, item, event);
|
|
136
|
+
}
|
|
137
|
+
if (event.type === 'recipe.materialized' && typeof value?.recipeId === 'string') {
|
|
138
|
+
const id = addEntityNode(nodes, 'compiler-recipe', value.recipeId);
|
|
139
|
+
const proposal = typeof value.proposalId === 'string' ? addEntityNode(nodes, 'proposal', value.proposalId) : null;
|
|
140
|
+
if (proposal)
|
|
141
|
+
addEdge(edges, { from: id, to: proposal, type: 'derived_from', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
142
|
+
if (Array.isArray(value.lineage))
|
|
143
|
+
for (const item of value.lineage)
|
|
144
|
+
if (item && typeof item === 'object')
|
|
145
|
+
lineageEdge(edges, nodes, id, item, event);
|
|
146
|
+
}
|
|
147
|
+
if (event.type === 'evolution.superseded') {
|
|
148
|
+
const transition = event.payload.transition;
|
|
149
|
+
if (transition && typeof transition === 'object' && !Array.isArray(transition)) {
|
|
150
|
+
const value = transition;
|
|
151
|
+
const from = typeof value.entity === 'string' && typeof value.entityId === 'string' ? addEntityNode(nodes, value.entity, value.entityId) : null;
|
|
152
|
+
const supersedes = value.supersedes;
|
|
153
|
+
if (from && supersedes && typeof supersedes === 'object' && !Array.isArray(supersedes)) {
|
|
154
|
+
const reference = supersedes;
|
|
155
|
+
lineageEdge(edges, nodes, from, reference, event);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (event.type === 'workspace.paths.changed') {
|
|
160
|
+
const payload = event.payload;
|
|
161
|
+
if (Array.isArray(payload.paths))
|
|
162
|
+
for (const item of payload.paths)
|
|
163
|
+
if (typeof item === 'string') {
|
|
164
|
+
const path = normalizePath(item);
|
|
165
|
+
if (!path)
|
|
166
|
+
continue;
|
|
167
|
+
const target = `path:${path}`;
|
|
168
|
+
addNode(nodes, target, 'path');
|
|
169
|
+
addEdge(edges, { from: workspace, to: target, type: 'observed_in', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (event.type === 'record.used') {
|
|
173
|
+
const used = event.payload;
|
|
174
|
+
if (typeof used.entity === 'string' && typeof used.entityId === 'string') {
|
|
175
|
+
const from = addEntityNode(nodes, used.entity, used.entityId);
|
|
176
|
+
if (from)
|
|
177
|
+
addEdge(edges, { from, to: workspace, type: 'used_for', sourceEventIds: [event.eventId], occurredAt: event.occurredAt });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
schemaVersion: 1,
|
|
183
|
+
derivationVersion: EVIDENCE_GRAPH_DERIVATION_VERSION,
|
|
184
|
+
nodes: [...nodes.values()].sort((a, b) => a.nodeId.localeCompare(b.nodeId)),
|
|
185
|
+
edges: [...edges.values()].sort((a, b) => a.edgeId.localeCompare(b.edgeId)),
|
|
186
|
+
};
|
|
187
|
+
}
|