claude-multi-session 1.0.0 → 2.2.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 +453 -0
- package/STRATEGY.md +306 -0
- package/bin/cli.js +1081 -18
- package/bin/continuity-hook.js +118 -0
- package/bin/setup.js +118 -63
- package/package.json +3 -2
- package/src/artifact-store.js +639 -0
- package/src/atomic-io.js +99 -0
- package/src/briefing-generator.js +451 -0
- package/src/continuity-hooks.js +253 -0
- package/src/contract-store.js +525 -0
- package/src/decision-journal.js +229 -0
- package/src/dependency-resolver.js +453 -0
- package/src/diff-engine.js +473 -0
- package/src/file-lock.js +161 -0
- package/src/index.js +26 -0
- package/src/lineage-graph.js +402 -0
- package/src/mcp-server.js +2062 -268
- package/src/pattern-registry.js +221 -0
- package/src/pipeline-engine.js +618 -0
- package/src/session-snapshot.js +508 -0
- package/src/snapshot-engine.js +490 -0
- package/src/stale-detector.js +169 -0
- package/src/team-hub.js +584 -0
package/bin/cli.js
CHANGED
|
@@ -9,27 +9,52 @@
|
|
|
9
9
|
* or: npx claude-multi-session <command> [options]
|
|
10
10
|
*
|
|
11
11
|
* Commands:
|
|
12
|
-
* spawn
|
|
13
|
-
* send
|
|
14
|
-
* resume
|
|
15
|
-
* pause
|
|
16
|
-
* fork
|
|
17
|
-
* stop
|
|
18
|
-
* kill
|
|
19
|
-
* status
|
|
20
|
-
* output
|
|
21
|
-
* list
|
|
22
|
-
* history
|
|
23
|
-
* delete
|
|
24
|
-
* batch
|
|
25
|
-
* cleanup
|
|
26
|
-
* delegate
|
|
27
|
-
* continue
|
|
28
|
-
*
|
|
29
|
-
*
|
|
12
|
+
* spawn Start a new streaming session
|
|
13
|
+
* send Send a message to an existing session
|
|
14
|
+
* resume Restore and resume a stopped/paused session
|
|
15
|
+
* pause Pause a session (keeps process alive)
|
|
16
|
+
* fork Branch off a session into a new one
|
|
17
|
+
* stop Gracefully stop a session
|
|
18
|
+
* kill Force kill a session
|
|
19
|
+
* status Show detailed session info
|
|
20
|
+
* output Get last response text
|
|
21
|
+
* list List all sessions
|
|
22
|
+
* history Show full interaction history
|
|
23
|
+
* delete Permanently remove a session
|
|
24
|
+
* batch Spawn multiple sessions from JSON file
|
|
25
|
+
* cleanup Remove old sessions
|
|
26
|
+
* delegate Smart task delegation with safety + control loop
|
|
27
|
+
* continue Send follow-up to a delegated session
|
|
28
|
+
* team-roster Show team members
|
|
29
|
+
* team-send Send message to teammate
|
|
30
|
+
* team-broadcast Send to all teammates
|
|
31
|
+
* team-inbox Check inbox
|
|
32
|
+
* artifact-publish Publish artifact
|
|
33
|
+
* artifact-get Read artifact
|
|
34
|
+
* artifact-list List artifacts
|
|
35
|
+
* contract-create Create contract
|
|
36
|
+
* contract-list List contracts
|
|
37
|
+
* contract-start Start contract
|
|
38
|
+
* pipeline-create Create pipeline
|
|
39
|
+
* pipeline-list List pipelines
|
|
40
|
+
* snapshot-create Create snapshot
|
|
41
|
+
* snapshot-list List snapshots
|
|
42
|
+
* snapshot-rollback Rollback to snapshot
|
|
43
|
+
* setup Register MCP server with Claude Code
|
|
44
|
+
* help Show this help
|
|
30
45
|
*/
|
|
31
46
|
|
|
32
47
|
const { SessionManager, Delegate } = require('../src/index');
|
|
48
|
+
const TeamHub = require('../src/team-hub');
|
|
49
|
+
const ArtifactStore = require('../src/artifact-store');
|
|
50
|
+
const ContractStore = require('../src/contract-store');
|
|
51
|
+
const PipelineEngine = require('../src/pipeline-engine');
|
|
52
|
+
const SnapshotEngine = require('../src/snapshot-engine');
|
|
53
|
+
const SessionSnapshot = require('../src/session-snapshot');
|
|
54
|
+
const BriefingGenerator = require('../src/briefing-generator');
|
|
55
|
+
const StaleDetector = require('../src/stale-detector');
|
|
56
|
+
const DecisionJournal = require('../src/decision-journal');
|
|
57
|
+
const PatternRegistry = require('../src/pattern-registry');
|
|
33
58
|
const fs = require('fs');
|
|
34
59
|
const path = require('path');
|
|
35
60
|
|
|
@@ -488,6 +513,922 @@ function cmdCleanup(mgr, flags) {
|
|
|
488
513
|
}
|
|
489
514
|
}
|
|
490
515
|
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Team/Artifact/Contract/Pipeline/Snapshot Commands
|
|
518
|
+
// ============================================================================
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* TEAM-ROSTER — Show team members
|
|
522
|
+
*/
|
|
523
|
+
function cmdTeamRoster(flags) {
|
|
524
|
+
const team = flags.team || 'default';
|
|
525
|
+
const hub = new TeamHub(team);
|
|
526
|
+
const roster = hub.getRoster();
|
|
527
|
+
|
|
528
|
+
console.log(`\n=== Team Roster: ${team} ===\n`);
|
|
529
|
+
console.log(`${'Name'.padEnd(25)} ${'Role'.padEnd(20)} ${'Status'.padEnd(12)} ${'Task'.padEnd(30)} ${'Last Seen'}`);
|
|
530
|
+
console.log('-'.repeat(120));
|
|
531
|
+
|
|
532
|
+
for (const member of roster) {
|
|
533
|
+
console.log(
|
|
534
|
+
`${truncate(member.name, 24).padEnd(25)} ` +
|
|
535
|
+
`${truncate(member.role || '-', 19).padEnd(20)} ` +
|
|
536
|
+
`${(member.status || 'offline').padEnd(12)} ` +
|
|
537
|
+
`${truncate(member.task || '-', 29).padEnd(30)} ` +
|
|
538
|
+
`${timeAgo(member.lastSeen)}`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
console.log(`\nTotal: ${roster.length}\n`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* TEAM-SEND — Send message to teammate
|
|
547
|
+
*/
|
|
548
|
+
function cmdTeamSend(flags) {
|
|
549
|
+
if (!flags.from) { console.error('Error: --from required'); process.exit(1); }
|
|
550
|
+
if (!flags.to) { console.error('Error: --to required'); process.exit(1); }
|
|
551
|
+
if (!flags.message) { console.error('Error: --message required'); process.exit(1); }
|
|
552
|
+
|
|
553
|
+
const team = flags.team || 'default';
|
|
554
|
+
const hub = new TeamHub(team);
|
|
555
|
+
|
|
556
|
+
hub.sendDirect(flags.from, flags.to, flags.message, flags.priority || 'normal');
|
|
557
|
+
console.log(`Message sent from "${flags.from}" to "${flags.to}".`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* TEAM-BROADCAST — Send to all teammates
|
|
562
|
+
*/
|
|
563
|
+
function cmdTeamBroadcast(flags) {
|
|
564
|
+
if (!flags.from) { console.error('Error: --from required'); process.exit(1); }
|
|
565
|
+
if (!flags.message) { console.error('Error: --message required'); process.exit(1); }
|
|
566
|
+
|
|
567
|
+
const team = flags.team || 'default';
|
|
568
|
+
const hub = new TeamHub(team);
|
|
569
|
+
|
|
570
|
+
hub.sendBroadcast(flags.from, flags.message, flags.priority || 'normal');
|
|
571
|
+
console.log(`Broadcast sent from "${flags.from}" to all team members.`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* TEAM-INBOX — Check inbox
|
|
576
|
+
*/
|
|
577
|
+
function cmdTeamInbox(flags) {
|
|
578
|
+
if (!flags.name) { console.error('Error: --name required'); process.exit(1); }
|
|
579
|
+
|
|
580
|
+
const team = flags.team || 'default';
|
|
581
|
+
const hub = new TeamHub(team);
|
|
582
|
+
const markRead = flags['mark-read'] !== undefined ? flags['mark-read'] : true;
|
|
583
|
+
const limit = flags.limit ? parseInt(flags.limit) : 20;
|
|
584
|
+
|
|
585
|
+
const messages = hub.getInbox(flags.name, markRead, limit);
|
|
586
|
+
|
|
587
|
+
console.log(`\n=== Inbox: ${flags.name} (${messages.length} messages) ===\n`);
|
|
588
|
+
|
|
589
|
+
if (messages.length === 0) {
|
|
590
|
+
console.log('No messages.\n');
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
for (const msg of messages) {
|
|
595
|
+
console.log(`[${msg.priority || 'normal'}] From: ${msg.from} | ${timeAgo(msg.timestamp)}`);
|
|
596
|
+
console.log(` ${msg.content}`);
|
|
597
|
+
if (msg.messageId) console.log(` ID: ${msg.messageId}`);
|
|
598
|
+
console.log('');
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* ARTIFACT-PUBLISH — Publish artifact
|
|
604
|
+
*/
|
|
605
|
+
function cmdArtifactPublish(flags) {
|
|
606
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
607
|
+
if (!flags.type) { console.error('Error: --type required'); process.exit(1); }
|
|
608
|
+
if (!flags.name) { console.error('Error: --name required'); process.exit(1); }
|
|
609
|
+
if (!flags.data) { console.error('Error: --data required (JSON string)'); process.exit(1); }
|
|
610
|
+
|
|
611
|
+
const team = flags.team || 'default';
|
|
612
|
+
const store = new ArtifactStore(team);
|
|
613
|
+
|
|
614
|
+
let data;
|
|
615
|
+
try {
|
|
616
|
+
data = JSON.parse(flags.data);
|
|
617
|
+
} catch (e) {
|
|
618
|
+
console.error(`Invalid JSON in --data: ${e.message}`);
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const artifact = store.publish(flags.id, {
|
|
623
|
+
type: flags.type,
|
|
624
|
+
name: flags.name,
|
|
625
|
+
data: data,
|
|
626
|
+
publisher: flags.publisher,
|
|
627
|
+
summary: flags.summary,
|
|
628
|
+
tags: flags.tags ? flags.tags.split(',') : [],
|
|
629
|
+
derivedFrom: flags['derived-from'] ? flags['derived-from'].split(',') : [],
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
console.log(`Artifact published: ${flags.id} (version ${artifact.version})`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* ARTIFACT-GET — Read artifact
|
|
637
|
+
*/
|
|
638
|
+
function cmdArtifactGet(flags) {
|
|
639
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
640
|
+
|
|
641
|
+
const team = flags.team || 'default';
|
|
642
|
+
const store = new ArtifactStore(team);
|
|
643
|
+
|
|
644
|
+
const artifact = store.get(flags.id, flags.version ? parseInt(flags.version) : undefined);
|
|
645
|
+
|
|
646
|
+
if (!artifact) {
|
|
647
|
+
console.log(`Artifact "${flags.id}" not found.`);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
console.log(`\n=== Artifact: ${artifact.artifactId} (v${artifact.version}) ===\n`);
|
|
652
|
+
console.log(`Type: ${artifact.type}`);
|
|
653
|
+
console.log(`Name: ${artifact.name}`);
|
|
654
|
+
console.log(`Publisher: ${artifact.publisher || 'unknown'}`);
|
|
655
|
+
console.log(`Published: ${artifact.timestamp}`);
|
|
656
|
+
if (artifact.summary) console.log(`Summary: ${artifact.summary}`);
|
|
657
|
+
if (artifact.tags && artifact.tags.length > 0) console.log(`Tags: ${artifact.tags.join(', ')}`);
|
|
658
|
+
console.log('\nData:');
|
|
659
|
+
console.log(JSON.stringify(artifact.data, null, 2));
|
|
660
|
+
console.log('');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* ARTIFACT-LIST — List artifacts
|
|
665
|
+
*/
|
|
666
|
+
function cmdArtifactList(flags) {
|
|
667
|
+
const team = flags.team || 'default';
|
|
668
|
+
const store = new ArtifactStore(team);
|
|
669
|
+
|
|
670
|
+
const artifacts = store.list(flags.type, flags.publisher, flags.tag);
|
|
671
|
+
|
|
672
|
+
console.log(`\n=== Artifacts (${artifacts.length}) ===\n`);
|
|
673
|
+
|
|
674
|
+
if (artifacts.length === 0) {
|
|
675
|
+
console.log('No artifacts.\n');
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
console.log(`${'ID'.padEnd(30)} ${'Type'.padEnd(20)} ${'Version'.padEnd(8)} ${'Publisher'.padEnd(15)} ${'Published'}`);
|
|
680
|
+
console.log('-'.repeat(100));
|
|
681
|
+
|
|
682
|
+
for (const a of artifacts) {
|
|
683
|
+
console.log(
|
|
684
|
+
`${truncate(a.artifactId, 29).padEnd(30)} ` +
|
|
685
|
+
`${truncate(a.type, 19).padEnd(20)} ` +
|
|
686
|
+
`${String(a.version).padEnd(8)} ` +
|
|
687
|
+
`${truncate(a.publisher || '-', 14).padEnd(15)} ` +
|
|
688
|
+
`${timeAgo(a.timestamp)}`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
console.log('');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* CONTRACT-CREATE — Create contract
|
|
697
|
+
*/
|
|
698
|
+
function cmdContractCreate(flags) {
|
|
699
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
700
|
+
if (!flags.title) { console.error('Error: --title required'); process.exit(1); }
|
|
701
|
+
if (!flags.assignee) { console.error('Error: --assignee required'); process.exit(1); }
|
|
702
|
+
if (!flags.assigner) { console.error('Error: --assigner required'); process.exit(1); }
|
|
703
|
+
|
|
704
|
+
const team = flags.team || 'default';
|
|
705
|
+
const store = new ContractStore(team);
|
|
706
|
+
|
|
707
|
+
const contract = store.create(flags.id, {
|
|
708
|
+
title: flags.title,
|
|
709
|
+
assignee: flags.assignee,
|
|
710
|
+
assigner: flags.assigner,
|
|
711
|
+
description: flags.description,
|
|
712
|
+
inputs: flags.inputs ? JSON.parse(flags.inputs) : {},
|
|
713
|
+
expectedOutputs: flags['expected-outputs'] ? JSON.parse(flags['expected-outputs']) : [],
|
|
714
|
+
acceptanceCriteria: flags['acceptance-criteria'] ? JSON.parse(flags['acceptance-criteria']) : [],
|
|
715
|
+
dependencies: flags.dependencies ? flags.dependencies.split(',') : [],
|
|
716
|
+
priority: flags.priority || 'normal',
|
|
717
|
+
timeoutMs: flags['timeout-ms'] ? parseInt(flags['timeout-ms']) : null,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
console.log(`Contract created: ${flags.id} (status: ${contract.status})`);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* CONTRACT-LIST — List contracts
|
|
725
|
+
*/
|
|
726
|
+
function cmdContractList(flags) {
|
|
727
|
+
const team = flags.team || 'default';
|
|
728
|
+
const store = new ContractStore(team);
|
|
729
|
+
|
|
730
|
+
const contracts = store.list(flags.status, flags.assignee, flags.assigner);
|
|
731
|
+
|
|
732
|
+
console.log(`\n=== Contracts (${contracts.length}) ===\n`);
|
|
733
|
+
|
|
734
|
+
if (contracts.length === 0) {
|
|
735
|
+
console.log('No contracts.\n');
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
console.log(`${'ID'.padEnd(25)} ${'Title'.padEnd(30)} ${'Status'.padEnd(15)} ${'Assignee'.padEnd(15)} ${'Created'}`);
|
|
740
|
+
console.log('-'.repeat(110));
|
|
741
|
+
|
|
742
|
+
for (const c of contracts) {
|
|
743
|
+
console.log(
|
|
744
|
+
`${truncate(c.contractId, 24).padEnd(25)} ` +
|
|
745
|
+
`${truncate(c.title, 29).padEnd(30)} ` +
|
|
746
|
+
`${(c.status || 'pending').padEnd(15)} ` +
|
|
747
|
+
`${truncate(c.assignee, 14).padEnd(15)} ` +
|
|
748
|
+
`${timeAgo(c.created)}`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
console.log('');
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* CONTRACT-START — Start contract
|
|
757
|
+
*/
|
|
758
|
+
function cmdContractStart(flags) {
|
|
759
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
760
|
+
|
|
761
|
+
const team = flags.team || 'default';
|
|
762
|
+
const store = new ContractStore(team);
|
|
763
|
+
|
|
764
|
+
const contract = store.start(flags.id);
|
|
765
|
+
console.log(`Contract "${flags.id}" started (status: ${contract.status})`);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* PIPELINE-CREATE — Create pipeline
|
|
770
|
+
*/
|
|
771
|
+
function cmdPipelineCreate(flags) {
|
|
772
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
773
|
+
if (!flags.rules) { console.error('Error: --rules required (JSON string)'); process.exit(1); }
|
|
774
|
+
|
|
775
|
+
const team = flags.team || 'default';
|
|
776
|
+
const engine = new PipelineEngine(team);
|
|
777
|
+
|
|
778
|
+
let rules;
|
|
779
|
+
try {
|
|
780
|
+
rules = JSON.parse(flags.rules);
|
|
781
|
+
} catch (e) {
|
|
782
|
+
console.error(`Invalid JSON in --rules: ${e.message}`);
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const pipeline = engine.create(flags.id, rules, flags.owner);
|
|
787
|
+
console.log(`Pipeline created: ${flags.id} (enabled: ${pipeline.enabled})`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* PIPELINE-LIST — List pipelines
|
|
792
|
+
*/
|
|
793
|
+
function cmdPipelineList(flags) {
|
|
794
|
+
const team = flags.team || 'default';
|
|
795
|
+
const engine = new PipelineEngine(team);
|
|
796
|
+
|
|
797
|
+
const pipelines = engine.list(flags.owner);
|
|
798
|
+
|
|
799
|
+
console.log(`\n=== Pipelines (${pipelines.length}) ===\n`);
|
|
800
|
+
|
|
801
|
+
if (pipelines.length === 0) {
|
|
802
|
+
console.log('No pipelines.\n');
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
console.log(`${'ID'.padEnd(25)} ${'Enabled'.padEnd(10)} ${'Rules'.padEnd(8)} ${'Executed'.padEnd(10)} ${'Owner'}`);
|
|
807
|
+
console.log('-'.repeat(80));
|
|
808
|
+
|
|
809
|
+
for (const p of pipelines) {
|
|
810
|
+
console.log(
|
|
811
|
+
`${truncate(p.pipelineId, 24).padEnd(25)} ` +
|
|
812
|
+
`${(p.enabled ? 'yes' : 'no').padEnd(10)} ` +
|
|
813
|
+
`${String(p.rules.length).padEnd(8)} ` +
|
|
814
|
+
`${String(p.executionCount || 0).padEnd(10)} ` +
|
|
815
|
+
`${p.owner || '-'}`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
console.log('');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* SNAPSHOT-CREATE — Create snapshot
|
|
824
|
+
*/
|
|
825
|
+
function cmdSnapshotCreate(flags) {
|
|
826
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
827
|
+
|
|
828
|
+
const team = flags.team || 'default';
|
|
829
|
+
const engine = new SnapshotEngine(team);
|
|
830
|
+
|
|
831
|
+
const snapshot = engine.createSnapshot(flags.id, flags.label, flags.description);
|
|
832
|
+
console.log(`Snapshot created: ${flags.id} at ${snapshot.timestamp}`);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* SNAPSHOT-LIST — List snapshots
|
|
837
|
+
*/
|
|
838
|
+
function cmdSnapshotList(flags) {
|
|
839
|
+
const team = flags.team || 'default';
|
|
840
|
+
const engine = new SnapshotEngine(team);
|
|
841
|
+
|
|
842
|
+
const snapshots = engine.listSnapshots();
|
|
843
|
+
|
|
844
|
+
console.log(`\n=== Snapshots (${snapshots.length}) ===\n`);
|
|
845
|
+
|
|
846
|
+
if (snapshots.length === 0) {
|
|
847
|
+
console.log('No snapshots.\n');
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
console.log(`${'ID'.padEnd(25)} ${'Label'.padEnd(30)} ${'Created'}`);
|
|
852
|
+
console.log('-'.repeat(70));
|
|
853
|
+
|
|
854
|
+
for (const s of snapshots) {
|
|
855
|
+
console.log(
|
|
856
|
+
`${truncate(s.snapshotId, 24).padEnd(25)} ` +
|
|
857
|
+
`${truncate(s.label || '-', 29).padEnd(30)} ` +
|
|
858
|
+
`${timeAgo(s.timestamp)}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
console.log('');
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* SNAPSHOT-ROLLBACK — Rollback to snapshot
|
|
867
|
+
*/
|
|
868
|
+
function cmdSnapshotRollback(flags) {
|
|
869
|
+
if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
|
|
870
|
+
|
|
871
|
+
const team = flags.team || 'default';
|
|
872
|
+
const engine = new SnapshotEngine(team);
|
|
873
|
+
|
|
874
|
+
engine.rollback(flags.id, flags['preserve-artifacts'] !== false);
|
|
875
|
+
console.log(`Rolled back to snapshot: ${flags.id}`);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// ============================================================================
|
|
879
|
+
// Session Continuity Commands (Layer 0)
|
|
880
|
+
// ============================================================================
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* SNAPSHOT — Capture a session snapshot
|
|
884
|
+
*/
|
|
885
|
+
function cmdSnapshot(flags) {
|
|
886
|
+
if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
|
|
887
|
+
|
|
888
|
+
const snap = new SessionSnapshot(flags['project-path']);
|
|
889
|
+
const sessionName = flags.session || 'cli-session';
|
|
890
|
+
const taskSummary = flags.task || 'Work in progress';
|
|
891
|
+
|
|
892
|
+
const result = snap.capture(sessionName, {
|
|
893
|
+
taskSummary: taskSummary,
|
|
894
|
+
activeFiles: flags.files ? flags.files.split(',') : [],
|
|
895
|
+
openQuestions: flags.questions ? flags.questions.split(',') : []
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
console.log(`Snapshot captured: ${result.snapshotId}`);
|
|
899
|
+
console.log(` Session: ${sessionName}`);
|
|
900
|
+
console.log(` Captured at: ${result.capturedAt}`);
|
|
901
|
+
console.log(` Files tracked: ${result.workingContext?.activeFiles?.length || 0}`);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* BRIEFING — Generate session briefing
|
|
906
|
+
*/
|
|
907
|
+
function cmdBriefing(flags) {
|
|
908
|
+
if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
|
|
909
|
+
|
|
910
|
+
const gen = new BriefingGenerator(flags['project-path']);
|
|
911
|
+
const maxTokens = flags['max-tokens'] ? parseInt(flags['max-tokens']) : 4000;
|
|
912
|
+
const includeDecisions = flags['include-decisions'] !== false;
|
|
913
|
+
const includePatterns = flags['include-patterns'] !== false;
|
|
914
|
+
|
|
915
|
+
const result = gen.generate({
|
|
916
|
+
maxTokens: maxTokens,
|
|
917
|
+
includeDecisions: includeDecisions,
|
|
918
|
+
includePatterns: includePatterns
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
console.log('\n' + result.markdown);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* STALE-CHECK — Check if context is stale
|
|
926
|
+
*/
|
|
927
|
+
function cmdStaleCheck(flags) {
|
|
928
|
+
if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
|
|
929
|
+
|
|
930
|
+
const detector = new StaleDetector(flags['project-path']);
|
|
931
|
+
const result = detector.check();
|
|
932
|
+
|
|
933
|
+
console.log(`\n=== Stale Context Check ===\n`);
|
|
934
|
+
console.log(`Status: ${result.isStale ? 'STALE' : 'FRESH'}`);
|
|
935
|
+
|
|
936
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
937
|
+
console.log(`\nWarnings:`);
|
|
938
|
+
result.warnings.forEach(w => console.log(` - ${w}`));
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (result.changedFiles && result.changedFiles.length > 0) {
|
|
942
|
+
console.log(`\nChanged files since last snapshot: ${result.changedFiles.length}`);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
console.log('');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* DECISIONS — List decisions
|
|
950
|
+
*/
|
|
951
|
+
function cmdDecisions(flags) {
|
|
952
|
+
if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
|
|
953
|
+
|
|
954
|
+
const journal = new DecisionJournal(flags['project-path']);
|
|
955
|
+
const limit = flags.limit ? parseInt(flags.limit) : 50;
|
|
956
|
+
const tag = flags.tag;
|
|
957
|
+
|
|
958
|
+
const decisions = journal.list({ limit: limit, tag: tag });
|
|
959
|
+
|
|
960
|
+
console.log(`\n=== Decisions (${decisions.length}) ===\n`);
|
|
961
|
+
|
|
962
|
+
if (decisions.length === 0) {
|
|
963
|
+
console.log('No decisions recorded.\n');
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
for (const d of decisions) {
|
|
968
|
+
console.log(`[${d.createdAt || 'unknown'}]`);
|
|
969
|
+
console.log(` Decision: ${d.decision}`);
|
|
970
|
+
if (d.reason) console.log(` Reason: ${d.reason}`);
|
|
971
|
+
if (d.tags && d.tags.length > 0) console.log(` Tags: ${d.tags.join(', ')}`);
|
|
972
|
+
console.log('');
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* PATTERNS — List patterns
|
|
978
|
+
*/
|
|
979
|
+
function cmdPatterns(flags) {
|
|
980
|
+
if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
|
|
981
|
+
|
|
982
|
+
const registry = new PatternRegistry(flags['project-path']);
|
|
983
|
+
const tag = flags.tag;
|
|
984
|
+
|
|
985
|
+
const patterns = registry.list({ tag: tag });
|
|
986
|
+
|
|
987
|
+
console.log(`\n=== Patterns (${patterns.length}) ===\n`);
|
|
988
|
+
|
|
989
|
+
if (patterns.length === 0) {
|
|
990
|
+
console.log('No patterns recorded.\n');
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
for (const p of patterns) {
|
|
995
|
+
console.log(`[${p.id}] ${p.rule}`);
|
|
996
|
+
if (p.context) console.log(` Context: ${p.context}`);
|
|
997
|
+
if (p.example) console.log(` Example: ${p.example}`);
|
|
998
|
+
if (p.tags && p.tags.length > 0) console.log(` Tags: ${p.tags.join(', ')}`);
|
|
999
|
+
console.log('');
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// ============================================================================
|
|
1004
|
+
// Session Continuity Automation Commands
|
|
1005
|
+
// ============================================================================
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* CONTINUITY-SETUP — Install Claude Code hooks for automatic session continuity.
|
|
1009
|
+
*
|
|
1010
|
+
* Writes 4 hook entries to ~/.claude/settings.json so that:
|
|
1011
|
+
* - SessionStart automatically injects a diff-aware briefing
|
|
1012
|
+
* - Stop quietly checkpoints in the background
|
|
1013
|
+
* - PreCompact saves context before compaction
|
|
1014
|
+
* - SessionEnd captures a final snapshot on exit
|
|
1015
|
+
*/
|
|
1016
|
+
function cmdContinuitySetup() {
|
|
1017
|
+
const os = require('os');
|
|
1018
|
+
const crypto = require('crypto');
|
|
1019
|
+
|
|
1020
|
+
// Resolve the absolute path to our hook entry point script
|
|
1021
|
+
const hookScript = path.resolve(__dirname, 'continuity-hook.js');
|
|
1022
|
+
|
|
1023
|
+
// Path to Claude Code's settings file
|
|
1024
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
1025
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
1026
|
+
|
|
1027
|
+
// Create ~/.claude/ directory if it doesn't exist
|
|
1028
|
+
if (!fs.existsSync(claudeDir)) {
|
|
1029
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Read existing settings (or start fresh)
|
|
1033
|
+
let settings = {};
|
|
1034
|
+
if (fs.existsSync(settingsPath)) {
|
|
1035
|
+
try {
|
|
1036
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
// If settings file is corrupted, start fresh
|
|
1039
|
+
settings = {};
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Make sure hooks object exists
|
|
1044
|
+
if (!settings.hooks) {
|
|
1045
|
+
settings.hooks = {};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// The hook command uses the absolute path to our entry point
|
|
1049
|
+
// On Windows, we need forward slashes in the JSON string for safety
|
|
1050
|
+
const hookScriptEscaped = hookScript.replace(/\\/g, '\\\\');
|
|
1051
|
+
const hookCmd = `node "${hookScriptEscaped}"`;
|
|
1052
|
+
|
|
1053
|
+
// Define the 4 hook configurations we need to install
|
|
1054
|
+
const hookConfigs = {
|
|
1055
|
+
SessionStart: {
|
|
1056
|
+
matcher: '',
|
|
1057
|
+
hooks: [{
|
|
1058
|
+
type: 'command',
|
|
1059
|
+
command: `${hookCmd} SessionStart`,
|
|
1060
|
+
timeout: 5
|
|
1061
|
+
}]
|
|
1062
|
+
},
|
|
1063
|
+
Stop: {
|
|
1064
|
+
matcher: '',
|
|
1065
|
+
hooks: [{
|
|
1066
|
+
type: 'command',
|
|
1067
|
+
command: `${hookCmd} Stop`,
|
|
1068
|
+
async: true,
|
|
1069
|
+
timeout: 10
|
|
1070
|
+
}]
|
|
1071
|
+
},
|
|
1072
|
+
PreCompact: {
|
|
1073
|
+
matcher: '',
|
|
1074
|
+
hooks: [{
|
|
1075
|
+
type: 'command',
|
|
1076
|
+
command: `${hookCmd} PreCompact`,
|
|
1077
|
+
timeout: 10
|
|
1078
|
+
}]
|
|
1079
|
+
},
|
|
1080
|
+
SessionEnd: {
|
|
1081
|
+
matcher: '',
|
|
1082
|
+
hooks: [{
|
|
1083
|
+
type: 'command',
|
|
1084
|
+
command: `${hookCmd} SessionEnd`,
|
|
1085
|
+
async: true,
|
|
1086
|
+
timeout: 15
|
|
1087
|
+
}]
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// Check if we're updating existing hooks (contains continuity-hook.js)
|
|
1092
|
+
let isUpdate = false;
|
|
1093
|
+
|
|
1094
|
+
// Merge hooks into settings — don't overwrite existing hooks from other tools
|
|
1095
|
+
for (const [eventName, newHookEntry] of Object.entries(hookConfigs)) {
|
|
1096
|
+
if (!settings.hooks[eventName]) {
|
|
1097
|
+
// No existing hooks for this event — create the array
|
|
1098
|
+
settings.hooks[eventName] = [newHookEntry];
|
|
1099
|
+
} else {
|
|
1100
|
+
// Existing hooks — check if our hook is already installed
|
|
1101
|
+
const existingArray = settings.hooks[eventName];
|
|
1102
|
+
let found = false;
|
|
1103
|
+
|
|
1104
|
+
for (let i = 0; i < existingArray.length; i++) {
|
|
1105
|
+
const entry = existingArray[i];
|
|
1106
|
+
// Check if any hook command in this entry contains our script
|
|
1107
|
+
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
1108
|
+
for (let j = 0; j < entry.hooks.length; j++) {
|
|
1109
|
+
if (entry.hooks[j].command && entry.hooks[j].command.includes('continuity-hook')) {
|
|
1110
|
+
// Update the existing hook with new path
|
|
1111
|
+
existingArray[i] = newHookEntry;
|
|
1112
|
+
found = true;
|
|
1113
|
+
isUpdate = true;
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (found) break;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (!found) {
|
|
1122
|
+
// Our hook isn't installed yet — add it to the array
|
|
1123
|
+
existingArray.push(newHookEntry);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Write the settings back using atomic write (write to temp, then rename)
|
|
1129
|
+
const tmpPath = settingsPath + '.tmp.' + Date.now();
|
|
1130
|
+
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
1131
|
+
fs.renameSync(tmpPath, settingsPath);
|
|
1132
|
+
|
|
1133
|
+
// Print success output
|
|
1134
|
+
if (isUpdate) {
|
|
1135
|
+
console.log(`\n Session Continuity hooks updated in ~/.claude/settings.json`);
|
|
1136
|
+
console.log(` (Hook script path updated to: ${hookScript})\n`);
|
|
1137
|
+
} else {
|
|
1138
|
+
console.log(`\n Session Continuity hooks installed to ~/.claude/settings.json\n`);
|
|
1139
|
+
console.log(` Hooks configured:`);
|
|
1140
|
+
console.log(` SessionStart -> Auto-inject session briefing (timeout: 5s)`);
|
|
1141
|
+
console.log(` Stop -> Background checkpoint (async, timeout: 10s)`);
|
|
1142
|
+
console.log(` PreCompact -> Critical save before compaction (timeout: 10s)`);
|
|
1143
|
+
console.log(` SessionEnd -> Final snapshot on exit (async, timeout: 15s)`);
|
|
1144
|
+
console.log(`\n Hook script: ${hookScript}`);
|
|
1145
|
+
console.log(`\n Continuity is now active for ALL projects.`);
|
|
1146
|
+
console.log(` Start a Claude Code session, do some work, exit, and start again.`);
|
|
1147
|
+
console.log(` The next session will automatically receive a briefing about what changed.\n`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* CONTINUITY-UNSETUP — Remove continuity hooks from ~/.claude/settings.json.
|
|
1153
|
+
*/
|
|
1154
|
+
function cmdContinuityUnsetup() {
|
|
1155
|
+
const os = require('os');
|
|
1156
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
1157
|
+
|
|
1158
|
+
// If settings file doesn't exist, nothing to remove
|
|
1159
|
+
if (!fs.existsSync(settingsPath)) {
|
|
1160
|
+
console.log('No settings file found at ~/.claude/settings.json — nothing to remove.');
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Read existing settings
|
|
1165
|
+
let settings;
|
|
1166
|
+
try {
|
|
1167
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1168
|
+
} catch (e) {
|
|
1169
|
+
console.error('Error reading settings file:', e.message);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (!settings.hooks) {
|
|
1174
|
+
console.log('No hooks found in settings — nothing to remove.');
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
let removedCount = 0;
|
|
1179
|
+
|
|
1180
|
+
// Remove any hook entries that contain 'continuity-hook' in their command
|
|
1181
|
+
for (const eventName of Object.keys(settings.hooks)) {
|
|
1182
|
+
const entries = settings.hooks[eventName];
|
|
1183
|
+
if (!Array.isArray(entries)) continue;
|
|
1184
|
+
|
|
1185
|
+
// Filter out entries that have continuity-hook commands
|
|
1186
|
+
settings.hooks[eventName] = entries.filter(entry => {
|
|
1187
|
+
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
1188
|
+
const hasContinuity = entry.hooks.some(h =>
|
|
1189
|
+
h.command && h.command.includes('continuity-hook')
|
|
1190
|
+
);
|
|
1191
|
+
if (hasContinuity) {
|
|
1192
|
+
removedCount++;
|
|
1193
|
+
return false; // Remove this entry
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return true; // Keep this entry
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
// Clean up empty arrays
|
|
1200
|
+
if (settings.hooks[eventName].length === 0) {
|
|
1201
|
+
delete settings.hooks[eventName];
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Clean up empty hooks object
|
|
1206
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
1207
|
+
delete settings.hooks;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Write back
|
|
1211
|
+
const tmpPath = settingsPath + '.tmp.' + Date.now();
|
|
1212
|
+
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
1213
|
+
fs.renameSync(tmpPath, settingsPath);
|
|
1214
|
+
|
|
1215
|
+
if (removedCount > 0) {
|
|
1216
|
+
console.log(`\n Removed ${removedCount} continuity hook(s) from ~/.claude/settings.json`);
|
|
1217
|
+
console.log(` Session Continuity automation is now disabled.\n`);
|
|
1218
|
+
} else {
|
|
1219
|
+
console.log('No continuity hooks found in settings — nothing to remove.');
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* CONTINUITY-RESET — Delete ALL continuity data for a project.
|
|
1225
|
+
*/
|
|
1226
|
+
function cmdContinuityReset(flags) {
|
|
1227
|
+
if (!flags['project-path']) {
|
|
1228
|
+
console.error('Error: --project-path required');
|
|
1229
|
+
process.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const os = require('os');
|
|
1233
|
+
const crypto = require('crypto');
|
|
1234
|
+
|
|
1235
|
+
const projectPath = flags['project-path'];
|
|
1236
|
+
|
|
1237
|
+
// Compute the same project hash used by all continuity modules
|
|
1238
|
+
const projectHash = crypto
|
|
1239
|
+
.createHash('sha256')
|
|
1240
|
+
.update(projectPath)
|
|
1241
|
+
.digest('hex')
|
|
1242
|
+
.slice(0, 16);
|
|
1243
|
+
|
|
1244
|
+
// Primary storage location: ~/.claude-multi-session/continuity/{hash}/
|
|
1245
|
+
const continuityDir = path.join(
|
|
1246
|
+
os.homedir(),
|
|
1247
|
+
'.claude-multi-session',
|
|
1248
|
+
'continuity',
|
|
1249
|
+
projectHash
|
|
1250
|
+
);
|
|
1251
|
+
|
|
1252
|
+
let deleted = false;
|
|
1253
|
+
|
|
1254
|
+
// Delete the primary continuity directory recursively
|
|
1255
|
+
if (fs.existsSync(continuityDir)) {
|
|
1256
|
+
fs.rmSync(continuityDir, { recursive: true, force: true });
|
|
1257
|
+
deleted = true;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Also check the old BriefingGenerator path (in case data was stored there before the fix)
|
|
1261
|
+
const oldBriefingsDir = path.join(
|
|
1262
|
+
os.homedir(),
|
|
1263
|
+
'.claude',
|
|
1264
|
+
'multi-session',
|
|
1265
|
+
projectHash,
|
|
1266
|
+
'briefings'
|
|
1267
|
+
);
|
|
1268
|
+
if (fs.existsSync(oldBriefingsDir)) {
|
|
1269
|
+
fs.rmSync(oldBriefingsDir, { recursive: true, force: true });
|
|
1270
|
+
deleted = true;
|
|
1271
|
+
|
|
1272
|
+
// Clean up the parent directory if it's now empty
|
|
1273
|
+
const oldParent = path.join(os.homedir(), '.claude', 'multi-session', projectHash);
|
|
1274
|
+
try {
|
|
1275
|
+
const remaining = fs.readdirSync(oldParent);
|
|
1276
|
+
if (remaining.length === 0) {
|
|
1277
|
+
fs.rmdirSync(oldParent);
|
|
1278
|
+
}
|
|
1279
|
+
} catch (e) {
|
|
1280
|
+
// Ignore — parent might not exist
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
if (deleted) {
|
|
1285
|
+
console.log(`\n Continuity data cleared for ${projectPath}`);
|
|
1286
|
+
console.log(` Project hash: ${projectHash}\n`);
|
|
1287
|
+
} else {
|
|
1288
|
+
console.log(`\n No continuity data found for ${projectPath}`);
|
|
1289
|
+
console.log(` Project hash: ${projectHash}\n`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* CONTINUITY-PRUNE — Keep only the N most recent snapshots for a project.
|
|
1295
|
+
*/
|
|
1296
|
+
function cmdContinuityPrune(flags) {
|
|
1297
|
+
if (!flags['project-path']) {
|
|
1298
|
+
console.error('Error: --project-path required');
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const projectPath = flags['project-path'];
|
|
1303
|
+
const keep = flags.keep ? parseInt(flags.keep) : 10;
|
|
1304
|
+
|
|
1305
|
+
// Instantiate SessionSnapshot to get the snapshots directory
|
|
1306
|
+
const snap = new SessionSnapshot(projectPath);
|
|
1307
|
+
const snapshotsDir = path.join(snap.getProjectDir(), 'snapshots');
|
|
1308
|
+
|
|
1309
|
+
if (!fs.existsSync(snapshotsDir)) {
|
|
1310
|
+
console.log('No snapshots directory found — nothing to prune.');
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Read all snapshot files
|
|
1315
|
+
const files = fs.readdirSync(snapshotsDir)
|
|
1316
|
+
.filter(f => f.startsWith('snap_') && f.endsWith('.json'));
|
|
1317
|
+
|
|
1318
|
+
if (files.length <= keep) {
|
|
1319
|
+
console.log(`\n Only ${files.length} snapshot(s) found — nothing to prune (keeping ${keep}).\n`);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Sort ascending by filename (contains timestamp) — oldest first
|
|
1324
|
+
files.sort();
|
|
1325
|
+
|
|
1326
|
+
// Delete the oldest files, keep the most recent
|
|
1327
|
+
const toDelete = files.slice(0, files.length - keep);
|
|
1328
|
+
let deletedCount = 0;
|
|
1329
|
+
|
|
1330
|
+
for (const file of toDelete) {
|
|
1331
|
+
try {
|
|
1332
|
+
fs.unlinkSync(path.join(snapshotsDir, file));
|
|
1333
|
+
deletedCount++;
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
console.error(` Warning: could not delete ${file}: ${e.message}`);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
console.log(`\n Pruned ${deletedCount} snapshot(s), kept ${keep} most recent.\n`);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* CONTINUITY-STATUS — Show continuity status at a glance.
|
|
1344
|
+
*/
|
|
1345
|
+
function cmdContinuityStatus(flags) {
|
|
1346
|
+
if (!flags['project-path']) {
|
|
1347
|
+
console.error('Error: --project-path required');
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const os = require('os');
|
|
1352
|
+
|
|
1353
|
+
const projectPath = flags['project-path'];
|
|
1354
|
+
|
|
1355
|
+
// Get snapshot info
|
|
1356
|
+
const snap = new SessionSnapshot(projectPath);
|
|
1357
|
+
const latest = snap.getLatest();
|
|
1358
|
+
const allSnapshots = snap.list(100);
|
|
1359
|
+
const projectHash = snap.getProjectHash();
|
|
1360
|
+
|
|
1361
|
+
// Get decision count
|
|
1362
|
+
const journal = new DecisionJournal(projectPath);
|
|
1363
|
+
const decisions = journal.list({});
|
|
1364
|
+
|
|
1365
|
+
// Get pattern count
|
|
1366
|
+
const registry = new PatternRegistry(projectPath);
|
|
1367
|
+
const patterns = registry.list({});
|
|
1368
|
+
|
|
1369
|
+
// Check staleness
|
|
1370
|
+
let staleStatus = 'N/A';
|
|
1371
|
+
let changedCount = 0;
|
|
1372
|
+
if (latest) {
|
|
1373
|
+
try {
|
|
1374
|
+
const detector = new StaleDetector(projectPath);
|
|
1375
|
+
const staleResult = detector.check();
|
|
1376
|
+
if (staleResult.isStale) {
|
|
1377
|
+
changedCount = staleResult.totalFileChanges || 0;
|
|
1378
|
+
staleStatus = `STALE -- ${staleResult.warnings.length} warning(s), ${changedCount} file change(s)`;
|
|
1379
|
+
} else {
|
|
1380
|
+
staleStatus = 'FRESH';
|
|
1381
|
+
}
|
|
1382
|
+
} catch (e) {
|
|
1383
|
+
staleStatus = 'Error checking: ' + e.message;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Check if hooks are installed
|
|
1388
|
+
let hooksInstalled = false;
|
|
1389
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
1390
|
+
if (fs.existsSync(settingsPath)) {
|
|
1391
|
+
try {
|
|
1392
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1393
|
+
if (settings.hooks) {
|
|
1394
|
+
// Check if any hook command contains continuity-hook
|
|
1395
|
+
const hookStr = JSON.stringify(settings.hooks);
|
|
1396
|
+
hooksInstalled = hookStr.includes('continuity-hook');
|
|
1397
|
+
}
|
|
1398
|
+
} catch (e) {
|
|
1399
|
+
// Ignore
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Format last snapshot time
|
|
1404
|
+
let lastSnapshotStr = 'None';
|
|
1405
|
+
let lastTrigger = 'N/A';
|
|
1406
|
+
let oldestStr = 'N/A';
|
|
1407
|
+
if (latest) {
|
|
1408
|
+
const capturedAt = new Date(latest.capturedAt);
|
|
1409
|
+
const ago = timeAgo(latest.capturedAt);
|
|
1410
|
+
lastSnapshotStr = `${capturedAt.toISOString()} (${ago})`;
|
|
1411
|
+
lastTrigger = latest.capturedBy || 'unknown';
|
|
1412
|
+
}
|
|
1413
|
+
if (allSnapshots.length > 0) {
|
|
1414
|
+
const oldest = allSnapshots[allSnapshots.length - 1];
|
|
1415
|
+
oldestStr = timeAgo(oldest.capturedAt);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Print the status
|
|
1419
|
+
console.log(`\n=== Session Continuity Status ===\n`);
|
|
1420
|
+
console.log(`Project: ${projectPath}`);
|
|
1421
|
+
console.log(`Project hash: ${projectHash}`);
|
|
1422
|
+
console.log(`Last snapshot: ${lastSnapshotStr}`);
|
|
1423
|
+
console.log(`Trigger: ${lastTrigger}`);
|
|
1424
|
+
console.log(`Snapshots stored: ${allSnapshots.length} (oldest: ${oldestStr})`);
|
|
1425
|
+
console.log(`Decisions logged: ${decisions.length}`);
|
|
1426
|
+
console.log(`Patterns saved: ${patterns.length}`);
|
|
1427
|
+
console.log(`Context status: ${staleStatus}`);
|
|
1428
|
+
console.log(`Hooks installed: ${hooksInstalled ? 'Yes (in ~/.claude/settings.json)' : 'No'}`);
|
|
1429
|
+
console.log('');
|
|
1430
|
+
}
|
|
1431
|
+
|
|
491
1432
|
function cmdHelp() {
|
|
492
1433
|
console.log(`
|
|
493
1434
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -558,6 +1499,103 @@ COMMANDS:
|
|
|
558
1499
|
[--global] [--local] [--uninstall]
|
|
559
1500
|
[--guide] [--no-guide]
|
|
560
1501
|
|
|
1502
|
+
TEAM COMMANDS:
|
|
1503
|
+
|
|
1504
|
+
team-roster Show team members
|
|
1505
|
+
[--team <name>]
|
|
1506
|
+
|
|
1507
|
+
team-send Send message to teammate
|
|
1508
|
+
--from <name> --to <name> --message <text>
|
|
1509
|
+
[--priority low|normal|high|urgent] [--team <name>]
|
|
1510
|
+
|
|
1511
|
+
team-broadcast Send to all teammates
|
|
1512
|
+
--from <name> --message <text>
|
|
1513
|
+
[--priority low|normal|high|urgent] [--team <name>]
|
|
1514
|
+
|
|
1515
|
+
team-inbox Check inbox
|
|
1516
|
+
--name <name> [--mark-read] [--limit <n>] [--team <name>]
|
|
1517
|
+
|
|
1518
|
+
ARTIFACT COMMANDS:
|
|
1519
|
+
|
|
1520
|
+
artifact-publish Publish artifact
|
|
1521
|
+
--id <id> --type <type> --name <name> --data <json>
|
|
1522
|
+
[--publisher <name>] [--summary <text>] [--tags <t1,t2>]
|
|
1523
|
+
[--derived-from <id1,id2>] [--team <name>]
|
|
1524
|
+
|
|
1525
|
+
artifact-get Read artifact
|
|
1526
|
+
--id <id> [--version <n>] [--team <name>]
|
|
1527
|
+
|
|
1528
|
+
artifact-list List artifacts
|
|
1529
|
+
[--type <type>] [--publisher <name>] [--tag <tag>] [--team <name>]
|
|
1530
|
+
|
|
1531
|
+
CONTRACT COMMANDS:
|
|
1532
|
+
|
|
1533
|
+
contract-create Create contract
|
|
1534
|
+
--id <id> --title <text> --assignee <name> --assigner <name>
|
|
1535
|
+
[--description <text>] [--priority low|normal|high|urgent]
|
|
1536
|
+
[--team <name>]
|
|
1537
|
+
|
|
1538
|
+
contract-list List contracts
|
|
1539
|
+
[--status pending|ready|in_progress|completed|failed]
|
|
1540
|
+
[--assignee <name>] [--assigner <name>] [--team <name>]
|
|
1541
|
+
|
|
1542
|
+
contract-start Start contract
|
|
1543
|
+
--id <id> [--team <name>]
|
|
1544
|
+
|
|
1545
|
+
PIPELINE COMMANDS:
|
|
1546
|
+
|
|
1547
|
+
pipeline-create Create pipeline
|
|
1548
|
+
--id <id> --rules <json> [--owner <name>] [--team <name>]
|
|
1549
|
+
|
|
1550
|
+
pipeline-list List pipelines
|
|
1551
|
+
[--owner <name>] [--team <name>]
|
|
1552
|
+
|
|
1553
|
+
SNAPSHOT COMMANDS:
|
|
1554
|
+
|
|
1555
|
+
snapshot-create Create snapshot
|
|
1556
|
+
--id <id> [--label <text>] [--description <text>] [--team <name>]
|
|
1557
|
+
|
|
1558
|
+
snapshot-list List snapshots
|
|
1559
|
+
[--team <name>]
|
|
1560
|
+
|
|
1561
|
+
snapshot-rollback Rollback to snapshot
|
|
1562
|
+
--id <id> [--preserve-artifacts] [--team <name>]
|
|
1563
|
+
|
|
1564
|
+
SESSION CONTINUITY COMMANDS (Layer 0):
|
|
1565
|
+
|
|
1566
|
+
snapshot Capture session snapshot
|
|
1567
|
+
--project-path <path> [--session <name>] [--task <summary>]
|
|
1568
|
+
[--files <file1,file2>] [--questions <q1,q2>]
|
|
1569
|
+
|
|
1570
|
+
briefing Generate session briefing
|
|
1571
|
+
--project-path <path> [--max-tokens <n>]
|
|
1572
|
+
[--include-decisions] [--include-patterns]
|
|
1573
|
+
|
|
1574
|
+
stale-check Check if context is stale
|
|
1575
|
+
--project-path <path>
|
|
1576
|
+
|
|
1577
|
+
decisions List decisions
|
|
1578
|
+
--project-path <path> [--limit <n>] [--tag <tag>]
|
|
1579
|
+
|
|
1580
|
+
patterns List patterns
|
|
1581
|
+
--project-path <path> [--tag <tag>]
|
|
1582
|
+
|
|
1583
|
+
CONTINUITY AUTOMATION COMMANDS:
|
|
1584
|
+
|
|
1585
|
+
continuity-setup Install hooks for automatic session continuity
|
|
1586
|
+
(writes to ~/.claude/settings.json)
|
|
1587
|
+
|
|
1588
|
+
continuity-unsetup Remove continuity hooks from settings
|
|
1589
|
+
|
|
1590
|
+
continuity-reset Delete ALL continuity data for a project
|
|
1591
|
+
--project-path <path>
|
|
1592
|
+
|
|
1593
|
+
continuity-prune Keep only N most recent snapshots
|
|
1594
|
+
--project-path <path> [--keep <n>]
|
|
1595
|
+
|
|
1596
|
+
continuity-status Show continuity status at a glance
|
|
1597
|
+
--project-path <path>
|
|
1598
|
+
|
|
561
1599
|
HOW IT WORKS:
|
|
562
1600
|
|
|
563
1601
|
Unlike the resume approach (new process per message), this system
|
|
@@ -659,6 +1697,31 @@ async function main() {
|
|
|
659
1697
|
cleanup: () => cmdCleanup(mgr, flags),
|
|
660
1698
|
delegate: () => cmdDelegate(mgr, flags),
|
|
661
1699
|
continue: () => cmdContinue(mgr, flags),
|
|
1700
|
+
'team-roster': () => cmdTeamRoster(flags),
|
|
1701
|
+
'team-send': () => cmdTeamSend(flags),
|
|
1702
|
+
'team-broadcast': () => cmdTeamBroadcast(flags),
|
|
1703
|
+
'team-inbox': () => cmdTeamInbox(flags),
|
|
1704
|
+
'artifact-publish': () => cmdArtifactPublish(flags),
|
|
1705
|
+
'artifact-get': () => cmdArtifactGet(flags),
|
|
1706
|
+
'artifact-list': () => cmdArtifactList(flags),
|
|
1707
|
+
'contract-create': () => cmdContractCreate(flags),
|
|
1708
|
+
'contract-list': () => cmdContractList(flags),
|
|
1709
|
+
'contract-start': () => cmdContractStart(flags),
|
|
1710
|
+
'pipeline-create': () => cmdPipelineCreate(flags),
|
|
1711
|
+
'pipeline-list': () => cmdPipelineList(flags),
|
|
1712
|
+
'snapshot-create': () => cmdSnapshotCreate(flags),
|
|
1713
|
+
'snapshot-list': () => cmdSnapshotList(flags),
|
|
1714
|
+
'snapshot-rollback': () => cmdSnapshotRollback(flags),
|
|
1715
|
+
snapshot: () => cmdSnapshot(flags),
|
|
1716
|
+
briefing: () => cmdBriefing(flags),
|
|
1717
|
+
'stale-check': () => cmdStaleCheck(flags),
|
|
1718
|
+
decisions: () => cmdDecisions(flags),
|
|
1719
|
+
patterns: () => cmdPatterns(flags),
|
|
1720
|
+
'continuity-setup': () => cmdContinuitySetup(),
|
|
1721
|
+
'continuity-unsetup': () => cmdContinuityUnsetup(),
|
|
1722
|
+
'continuity-reset': () => cmdContinuityReset(flags),
|
|
1723
|
+
'continuity-prune': () => cmdContinuityPrune(flags),
|
|
1724
|
+
'continuity-status': () => cmdContinuityStatus(flags),
|
|
662
1725
|
setup: () => cmdSetup(flags),
|
|
663
1726
|
help: () => cmdHelp(),
|
|
664
1727
|
};
|