fluffy-context 0.7.7 → 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 +97 -36
- package/dist/src/agent/api.d.ts +41 -0
- package/dist/src/agent/api.js +37 -0
- package/dist/src/agent/index.d.ts +6 -1
- package/dist/src/cli/main.js +244 -6
- 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 +130 -1
- 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 +181 -1
- package/dist/src/compiler/compile.js +72 -3
- 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 +2 -0
- package/dist/src/compiler/sources.d.ts +3 -1
- package/dist/src/compiler/sources.js +10 -1
- package/dist/src/compiler/types.d.ts +22 -1
- package/dist/src/mcp/server.js +202 -1
- 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 +33 -5
package/dist/src/cli/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { checkpoint } from '../runtime/runtime.js';
|
|
3
|
-
import { contextCompile, contextExpand, contextOrient, contextUsageReport, loadContext } from '../agent/api.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
4
|
import { formatUsageReport } from '../cognition/usage-report.js';
|
|
5
5
|
import { importV1, verifyV1Import } from '../cognition/migration-v1.js';
|
|
6
6
|
import { recordUse } from '../cognition/feedback.js';
|
|
@@ -74,6 +74,16 @@ function enumOption(args, name, values) {
|
|
|
74
74
|
throw new Error(`${name} must be one of: ${values.join(', ')}`);
|
|
75
75
|
return value;
|
|
76
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]; }));
|
|
77
87
|
const HELP = {
|
|
78
88
|
init: `usage: ctx init [path] [--path <path>]
|
|
79
89
|
|
|
@@ -152,6 +162,8 @@ Compile bounded, provider-neutral context with source provenance and selection e
|
|
|
152
162
|
Options:
|
|
153
163
|
--path <path> Project path
|
|
154
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
|
|
155
167
|
--scenario <scenario> resume|implementation|debugging|verification|handoff|exploration|unknown
|
|
156
168
|
--scope <scope> Exact consensus scope
|
|
157
169
|
--paths <paths> Comma-separated project-relative paths
|
|
@@ -173,6 +185,8 @@ Options:
|
|
|
173
185
|
--max-chars <number> Maximum expanded characters
|
|
174
186
|
--compile-max-chars <number> Original compile budget (default: 4000)
|
|
175
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
|
|
176
190
|
--query <text> Original compile query
|
|
177
191
|
--scenario <scenario> resume|implementation|debugging|verification|handoff|exploration|unknown
|
|
178
192
|
--scope <scope> Exact consensus scope
|
|
@@ -286,6 +300,13 @@ Record explicit use of verified v2 retrieval material.`,
|
|
|
286
300
|
'feedback use': `usage: ctx feedback use knowledge|deadend <id> --event-id <stable-id> [--path <path>]
|
|
287
301
|
|
|
288
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>]`,
|
|
289
310
|
journal: `usage: ctx journal verify [--path <path>]
|
|
290
311
|
|
|
291
312
|
Validate the v2 journal hash chain and replay projections in memory.`,
|
|
@@ -352,9 +373,49 @@ Rebuild v2 projections and verify imported v1 parity.
|
|
|
352
373
|
|
|
353
374
|
Options:
|
|
354
375
|
--path <path> Project path`,
|
|
355
|
-
|
|
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>]
|
|
356
389
|
|
|
357
|
-
|
|
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.
|
|
403
|
+
|
|
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>]`,
|
|
358
419
|
filesystem: `usage: ctx filesystem enable|disable|status [--path <path>]
|
|
359
420
|
|
|
360
421
|
Control whether the Runtime records filtered file metadata changes.`,
|
|
@@ -367,6 +428,9 @@ Disable filesystem change observation and restart a running Runtime.`,
|
|
|
367
428
|
'filesystem status': `usage: ctx filesystem status [--path <path>]
|
|
368
429
|
|
|
369
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.`,
|
|
370
434
|
'runtime status': `usage: ctx runtime status [--path <path>]
|
|
371
435
|
|
|
372
436
|
Show the read-only runtime policy, migration and authenticated lease status.
|
|
@@ -390,7 +454,7 @@ Stop the local runtime through authenticated loopback IPC.`,
|
|
|
390
454
|
Internal runtime daemon entry point.`,
|
|
391
455
|
};
|
|
392
456
|
function usage() {
|
|
393
|
-
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|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]
|
|
394
458
|
|
|
395
459
|
Run \"ctx <command> --help\" for command details.`;
|
|
396
460
|
}
|
|
@@ -418,6 +482,8 @@ function printHelp(args) {
|
|
|
418
482
|
|| (command === 'journal' && args[1] === 'verify')
|
|
419
483
|
|| (command === 'note' && ['add', 'list'].includes(args[1]))
|
|
420
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]))
|
|
421
487
|
|| (command === 'runtime' && ['enable', 'disable', 'start', 'stop', 'status', 'serve'].includes(args[1]))
|
|
422
488
|
|| (command === 'usage' && args[1] === 'report');
|
|
423
489
|
const key = nested ? `${command} ${args[1]}` : command;
|
|
@@ -497,12 +563,14 @@ async function run(args) {
|
|
|
497
563
|
return;
|
|
498
564
|
}
|
|
499
565
|
case 'compile': {
|
|
500
|
-
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'];
|
|
501
567
|
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
502
568
|
const query = positionals(args.slice(1), valueOptions);
|
|
503
569
|
validatePositionals(query, 1, HELP.compile);
|
|
504
570
|
print(await contextCompile(target, {
|
|
505
571
|
contextId: option(args, '--context'),
|
|
572
|
+
recipeId: option(args, '--recipe-id'),
|
|
573
|
+
graphHops: graphHopsOption(args),
|
|
506
574
|
query: query[0],
|
|
507
575
|
scenario: enumOption(args, '--scenario', ['resume', 'implementation', 'debugging', 'verification', 'handoff', 'exploration', 'unknown']),
|
|
508
576
|
scope: option(args, '--scope'),
|
|
@@ -516,7 +584,7 @@ async function run(args) {
|
|
|
516
584
|
return;
|
|
517
585
|
}
|
|
518
586
|
case 'expand': {
|
|
519
|
-
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'];
|
|
520
588
|
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
521
589
|
validatePositionals(positionals(args.slice(1), valueOptions), 0, HELP.expand);
|
|
522
590
|
const level = enumOption(args, '--level', ['structured', 'evidence']);
|
|
@@ -535,6 +603,8 @@ async function run(args) {
|
|
|
535
603
|
maxChars: option(args, '--max-chars') === undefined ? undefined : numericOption(args, '--max-chars', 0),
|
|
536
604
|
compile: {
|
|
537
605
|
contextId: option(args, '--context'),
|
|
606
|
+
recipeId: option(args, '--recipe-id'),
|
|
607
|
+
graphHops: graphHopsOption(args),
|
|
538
608
|
query: option(args, '--query'),
|
|
539
609
|
scenario: enumOption(args, '--scenario', ['resume', 'implementation', 'debugging', 'verification', 'handoff', 'exploration', 'unknown']),
|
|
540
610
|
scope: option(args, '--scope'),
|
|
@@ -678,6 +748,174 @@ async function run(args) {
|
|
|
678
748
|
}
|
|
679
749
|
return;
|
|
680
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
|
+
}
|
|
681
919
|
case 'journal': {
|
|
682
920
|
if (args[1] !== 'verify')
|
|
683
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EvolutionArtifactKind, EvolutionArtifactStatus, EvolutionLineageReference, EvolutionProposalState } from './types.js';
|
|
2
|
+
type ProposalTargetKind = Extract<EvolutionArtifactKind, 'skill' | 'compiler-recipe'>;
|
|
3
|
+
export interface ProposeEvolutionInput {
|
|
4
|
+
eventIds?: string[];
|
|
5
|
+
recentLimit?: number;
|
|
6
|
+
targetKind?: ProposalTargetKind;
|
|
7
|
+
}
|
|
8
|
+
export interface ProposeEvolutionResult {
|
|
9
|
+
experienceId: string;
|
|
10
|
+
experienceEventId: string;
|
|
11
|
+
proposalId: string | null;
|
|
12
|
+
proposalEventId: string | null;
|
|
13
|
+
recorded: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface AcceptEvolutionCandidateInput {
|
|
16
|
+
proposalId: string;
|
|
17
|
+
rationale: string;
|
|
18
|
+
}
|
|
19
|
+
export interface TransitionEvolutionProposalInput {
|
|
20
|
+
proposalId: string;
|
|
21
|
+
to: Extract<EvolutionArtifactStatus, 'verified' | 'rejected' | 'deprecated' | 'superseded'>;
|
|
22
|
+
rationale: string;
|
|
23
|
+
supersedes?: EvolutionLineageReference;
|
|
24
|
+
}
|
|
25
|
+
export declare function proposeEvolution(startPath: string | undefined, input?: ProposeEvolutionInput): Promise<ProposeEvolutionResult>;
|
|
26
|
+
export declare function listEvolutionProposals(startPath?: string): Promise<EvolutionProposalState[]>;
|
|
27
|
+
export declare function inspectEvolutionProposal(startPath: string | undefined, proposalId: string): Promise<EvolutionProposalState>;
|
|
28
|
+
export declare function acceptEvolutionCandidate(startPath: string | undefined, input: AcceptEvolutionCandidateInput): Promise<{
|
|
29
|
+
accepted: boolean;
|
|
30
|
+
eventId: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function transitionEvolutionProposal(startPath: string | undefined, input: TransitionEvolutionProposalInput): Promise<{
|
|
33
|
+
transitioned: boolean;
|
|
34
|
+
eventId: string;
|
|
35
|
+
}>;
|
|
36
|
+
export {};
|