tsunami-memory 1.0.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +501 -0
  3. package/README.zh-CN.md +485 -0
  4. package/package.json +46 -0
  5. package/server/api.ts +125 -0
  6. package/server/mcp.ts +221 -0
  7. package/src/bun_memory_store.ts +340 -0
  8. package/src/classifier_keywords.ts +115 -0
  9. package/src/core/project_state.ts +88 -0
  10. package/src/index.ts +54 -0
  11. package/src/legacy_compat/tsunami_compat.ts +22 -0
  12. package/src/legacy_compat/tsunami_legacy_identity.ts +13 -0
  13. package/src/legacy_compat/tsunami_legacy_taxonomy.ts +197 -0
  14. package/src/memory_audit.ts +32 -0
  15. package/src/memory_conflict_resolver.ts +14 -0
  16. package/src/memory_fabric.ts +31 -0
  17. package/src/memory_manager.ts +10 -0
  18. package/src/memory_promotion.ts +22 -0
  19. package/src/memory_recovery.ts +14 -0
  20. package/src/memory_runtime.ts +7 -0
  21. package/src/migration.ts +163 -0
  22. package/src/provider.ts +68 -0
  23. package/src/runtime/checkpoints/durable_recovery.ts +24 -0
  24. package/src/runtime/paths.ts +11 -0
  25. package/src/storm/basins.ts +57 -0
  26. package/src/storm/boundary.ts +52 -0
  27. package/src/storm/budget.ts +42 -0
  28. package/src/storm/center.ts +396 -0
  29. package/src/storm/confidence.ts +88 -0
  30. package/src/storm/coverage.ts +44 -0
  31. package/src/storm/directive.ts +94 -0
  32. package/src/storm/gate.ts +43 -0
  33. package/src/storm/helpers.ts +172 -0
  34. package/src/storm/horizon.ts +52 -0
  35. package/src/storm/intake.ts +80 -0
  36. package/src/storm/mode.ts +21 -0
  37. package/src/storm/pressure.ts +56 -0
  38. package/src/storm/readiness.ts +29 -0
  39. package/src/storm/saturation.ts +45 -0
  40. package/src/storm/selection.ts +49 -0
  41. package/src/storm/signals.ts +105 -0
  42. package/src/storm/types.ts +216 -0
  43. package/src/tsunami_bun_backend.ts +705 -0
  44. package/src/tsunami_chinese_dialect.ts +19 -0
  45. package/src/tsunami_classifier.ts +137 -0
  46. package/src/tsunami_client.ts +710 -0
  47. package/src/tsunami_execution_gate.ts +232 -0
  48. package/src/tsunami_graph_runtime.ts +359 -0
  49. package/src/tsunami_identity.ts +35 -0
  50. package/src/tsunami_routing.ts +169 -0
  51. package/src/tsunami_runtime_graph_sync.ts +17 -0
  52. package/src/tsunami_schema.ts +403 -0
  53. package/src/tsunami_storage_paths.ts +8 -0
  54. package/src/tsunami_storm_center.ts +53 -0
@@ -0,0 +1,45 @@
1
+ import type { TsunamiStormSelection, TsunamiStormSaturation } from './types';
2
+
3
+ export function buildStormSaturation(input: {
4
+ stormSelection?: TsunamiStormSelection;
5
+ selectedSignals: number;
6
+ totalSignals: number;
7
+ selectedEvidence: number;
8
+ totalEvidence: number;
9
+ selectedRelations: number;
10
+ totalRelations: number;
11
+ }): TsunamiStormSaturation | undefined {
12
+ const selection = input.stormSelection;
13
+ if (!selection) return undefined;
14
+
15
+ const signalHitLimit =
16
+ input.totalSignals > input.selectedSignals
17
+ && input.selectedSignals >= selection.signalLimit;
18
+ const evidenceHitLimit =
19
+ input.totalEvidence > input.selectedEvidence
20
+ && input.selectedEvidence >= selection.evidenceLimit;
21
+ const relationHitLimit =
22
+ input.totalRelations > input.selectedRelations
23
+ && input.selectedRelations >= selection.relationLimit;
24
+
25
+ const hitLanes: Array<'signals' | 'evidence' | 'relations'> = [];
26
+ if (signalHitLimit) hitLanes.push('signals');
27
+ if (evidenceHitLimit) hitLanes.push('evidence');
28
+ if (relationHitLimit) hitLanes.push('relations');
29
+
30
+ const level: TsunamiStormSaturation['level'] =
31
+ hitLanes.length >= 2 ? 'saturated' : hitLanes.length === 1 ? 'near_limit' : 'clear';
32
+ const reason =
33
+ hitLanes.length > 0
34
+ ? `${hitLanes.join(', ')} lane${hitLanes.length > 1 ? 's' : ''} hit the current storm caps while more support stayed outside the center`
35
+ : 'selected storm surface still fits within current storm caps';
36
+
37
+ return {
38
+ level,
39
+ signalHitLimit,
40
+ evidenceHitLimit,
41
+ relationHitLimit,
42
+ hitLanes,
43
+ reason,
44
+ };
45
+ }
@@ -0,0 +1,49 @@
1
+ import type { TsunamiStormBudget, TsunamiStormBoundary, TsunamiStormGate, TsunamiStormHorizon, TsunamiStormSelection } from './types';
2
+
3
+ export function buildStormSelection(input: {
4
+ stormBudget?: TsunamiStormBudget;
5
+ stormBoundary?: TsunamiStormBoundary;
6
+ stormGate?: TsunamiStormGate;
7
+ stormHorizon?: TsunamiStormHorizon;
8
+ }): TsunamiStormSelection | undefined {
9
+ const budget = input.stormBudget;
10
+ const boundary = input.stormBoundary;
11
+ const gate = input.stormGate;
12
+ const horizon = input.stormHorizon;
13
+ if (!budget && !boundary && !gate && !horizon) return undefined;
14
+
15
+ if (budget?.mode === 'frozen' || gate?.verdict === 'hold') {
16
+ return {
17
+ profile: 'frozen',
18
+ signalLimit: 3,
19
+ evidenceLimit: 1,
20
+ relationLimit: 4,
21
+ reason: budget?.reason || gate?.reason || 'hold the storm to the minimum observable surface while support stabilizes',
22
+ };
23
+ }
24
+ if (budget?.mode === 'minimal' || boundary?.mode === 'spilling' || boundary?.mode === 'permeable') {
25
+ return {
26
+ profile: 'tight',
27
+ signalLimit: 3,
28
+ evidenceLimit: 2,
29
+ relationLimit: 6,
30
+ reason: budget?.reason || boundary?.reason || 'keep the storm tightly sampled while widening support carefully',
31
+ };
32
+ }
33
+ if (budget?.mode === 'open' || gate?.verdict === 'expand' || (horizon?.steps || 0) >= 3) {
34
+ return {
35
+ profile: 'broad',
36
+ signalLimit: 6,
37
+ evidenceLimit: 5,
38
+ relationLimit: 14,
39
+ reason: budget?.reason || gate?.reason || horizon?.reason || 'support is strong enough to gather a wider storm surface',
40
+ };
41
+ }
42
+ return {
43
+ profile: 'focused',
44
+ signalLimit: 4,
45
+ evidenceLimit: 3,
46
+ relationLimit: 10,
47
+ reason: budget?.reason || 'hold a focused storm surface around the mainline while keeping enough support visible',
48
+ };
49
+ }
@@ -0,0 +1,105 @@
1
+ // Storm center — signal analysis
2
+ import { clampEnergy, safeTrim, buildProjectNode, buildTaskThreadNode, buildHandoffNode, buildAnchorNode, buildRecoveryNode } from './helpers';
3
+ import type { StormCurrentKind, TsunamiStormCenterCurrent, TsunamiStormCenterCurrentMix, TsunamiStormCenter } from './types';
4
+
5
+ export function buildCurrents(input: {
6
+ thread: ProjectTaskThread | null;
7
+ handoff: ProjectHandoffRecord | null;
8
+ anchors: TsunamiStormCenter['anchors'];
9
+ recovery: DurableRecoveryRecord | null;
10
+ evidence: TsunamiStormCenter['evidence'];
11
+ issues: TsunamiStormCenter['issues'];
12
+ repairSuggestions: TsunamiStormCenter['repairSuggestions'];
13
+ signalLimit: number;
14
+ fallbackMainlineTitle?: string;
15
+ fallbackMainlineSummary?: string;
16
+ }): TsunamiStormCenterCurrent[] {
17
+ const currents: TsunamiStormCenterCurrent[] = [];
18
+ if (input.thread) {
19
+ currents.push({
20
+ kind: 'primary_thread',
21
+ label: input.thread.title,
22
+ node: buildTaskThreadNode(input.thread.id),
23
+ energy: clampEnergy(input.thread.status === 'doing' ? 1 : input.thread.status === 'blocked' ? 0.82 : 0.74),
24
+ detail: safeTrim(input.thread.summary || input.thread.nextStep, 140),
25
+ });
26
+ }
27
+ if (!input.thread && !input.handoff && input.fallbackMainlineTitle) {
28
+ currents.push({
29
+ kind: 'primary_thread',
30
+ label: input.fallbackMainlineTitle,
31
+ energy: 0.66,
32
+ detail: safeTrim(input.fallbackMainlineSummary, 140),
33
+ });
34
+ }
35
+ if (input.handoff) {
36
+ currents.push({
37
+ kind: 'handoff',
38
+ label: input.handoff.task,
39
+ node: buildHandoffNode(input.handoff.id),
40
+ energy: clampEnergy(input.handoff.progressStatus === 'doing' ? 0.94 : 0.8),
41
+ detail: safeTrim(input.handoff.nextStep || input.handoff.summary, 140),
42
+ });
43
+ }
44
+ if (input.anchors[0]) {
45
+ const anchor = input.anchors[0];
46
+ currents.push({
47
+ kind: 'anchor',
48
+ label: anchor.title,
49
+ node: buildAnchorNode(anchor.pageId),
50
+ energy: clampEnergy(0.7 + Math.min(0.25, anchor.confidence * 0.2)),
51
+ detail: safeTrim(anchor.summary, 140),
52
+ });
53
+ }
54
+ if (input.recovery) {
55
+ currents.push({
56
+ kind: 'recovery',
57
+ label: input.recovery.recoveryId,
58
+ node: buildRecoveryNode(input.recovery.recoveryId),
59
+ energy: clampEnergy(0.62 + Math.min(0.25, Number(input.recovery.lineageDepth ?? 0) * 0.06)),
60
+ detail: safeTrim(input.recovery.note || input.recovery.source, 140),
61
+ });
62
+ }
63
+ for (const suggestion of input.repairSuggestions.slice(0, input.signalLimit)) {
64
+ currents.push({
65
+ kind: 'repair',
66
+ label: suggestion.title,
67
+ energy: clampEnergy(suggestion.priority === 'P0' ? 0.88 : suggestion.priority === 'P1' ? 0.76 : 0.62),
68
+ detail: safeTrim(suggestion.detail, 140),
69
+ });
70
+ }
71
+ for (const issue of input.issues.slice(0, input.signalLimit)) {
72
+ currents.push({
73
+ kind: 'issue',
74
+ label: issue.code,
75
+ energy: clampEnergy(issue.severity === 'high' ? 0.84 : issue.severity === 'medium' ? 0.68 : 0.52),
76
+ detail: safeTrim(issue.detail, 140),
77
+ });
78
+ }
79
+ for (const snippet of input.evidence.slice(0, input.signalLimit)) {
80
+ currents.push({
81
+ kind: 'evidence',
82
+ label: snippet.title,
83
+ energy: 0.58,
84
+ detail: safeTrim(snippet.quote, 140),
85
+ });
86
+ }
87
+ return currents.sort((a, b) => b.energy - a.energy || a.kind.localeCompare(b.kind));
88
+ }
89
+ export function buildCurrentMix(currents: TsunamiStormCenterCurrent[]): TsunamiStormCenterCurrentMix[] {
90
+ const kindMap = new Map<StormCurrentKind, { energy: number; count: number }>();
91
+ for (const current of currents) {
92
+ const entry = kindMap.get(current.kind) ?? { energy: 0, count: 0 };
93
+ entry.energy += current.energy;
94
+ entry.count += 1;
95
+ kindMap.set(current.kind, entry);
96
+ }
97
+ return Array.from(kindMap.entries())
98
+ .map(([kind, entry]) => ({
99
+ kind,
100
+ energy: Number(entry.energy.toFixed(2)),
101
+ count: entry.count,
102
+ }))
103
+ .sort((a, b) => b.energy - a.energy || b.count - a.count || a.kind.localeCompare(b.kind))
104
+ .slice(0, 4);
105
+ }
@@ -0,0 +1,216 @@
1
+ // Storm center — type definitions
2
+ import type { ProjectHandoffRecord, ProjectTaskThread } from '../core/project_state';
3
+ import type { DurableRecoveryRecord } from '../runtime/checkpoints/durable_recovery';
4
+ import type { TsunamiRuntimeGraphSyncSummary } from '../tsunami_runtime_graph_sync';
5
+
6
+ export type BuildStormCenterOpts = {
7
+ projectDir?: string;
8
+ query?: string;
9
+ sessionId?: string;
10
+ refreshGraph?: boolean;
11
+ evidenceLimit?: number;
12
+ signalLimit?: number;
13
+ relationLimit?: number;
14
+ };
15
+
16
+ type StormCurrentKind =
17
+ | 'primary_thread'
18
+ | 'handoff'
19
+ | 'anchor'
20
+ | 'recovery'
21
+ | 'repair'
22
+ | 'issue'
23
+ | 'evidence';
24
+
25
+ export type TsunamiStormCenterCurrent = {
26
+ kind: StormCurrentKind;
27
+ label: string;
28
+ node?: string;
29
+ energy: number;
30
+ detail?: string;
31
+ };
32
+
33
+ export type TsunamiStormCenterCurrentMix = {
34
+ kind: StormCurrentKind;
35
+ energy: number;
36
+ count: number;
37
+ };
38
+
39
+ export type TsunamiStormCenterStormMode = {
40
+ label: string;
41
+ dominantKind: StormCurrentKind;
42
+ dominance: number;
43
+ mixed: boolean;
44
+ };
45
+
46
+ export type TsunamiStormPressure = {
47
+ level: 'calm' | 'steady' | 'rising' | 'critical';
48
+ score: number;
49
+ reasons: string[];
50
+ };
51
+
52
+ export type TsunamiStormDirective = {
53
+ label: string;
54
+ lane: 'stabilize' | 'repair' | 'diagnose' | 'consolidate' | 'advance';
55
+ reason: string;
56
+ };
57
+
58
+ export type TsunamiStormAction = {
59
+ label: string;
60
+ target?: string;
61
+ reason: string;
62
+ };
63
+
64
+ export type TsunamiStormReadiness = {
65
+ level: 'weak' | 'partial' | 'ready' | 'fortified';
66
+ score: number;
67
+ gaps: string[];
68
+ };
69
+
70
+ export type TsunamiStormBoundary = {
71
+ mode: 'sealed' | 'guarded' | 'permeable' | 'spilling';
72
+ expand: boolean;
73
+ reason: string;
74
+ };
75
+
76
+ export type TsunamiStormHorizon = {
77
+ label: 'single_step' | 'short_run' | 'two_step' | 'multi_step';
78
+ steps: number;
79
+ reason: string;
80
+ };
81
+
82
+ export type TsunamiStormConfidence = {
83
+ level: 'low' | 'guarded' | 'confident' | 'high';
84
+ score: number;
85
+ reason: string;
86
+ };
87
+
88
+ export type TsunamiStormGate = {
89
+ verdict: 'hold' | 'guarded' | 'proceed' | 'expand';
90
+ allowForward: boolean;
91
+ reason: string;
92
+ };
93
+
94
+ export type TsunamiStormBudget = {
95
+ mode: 'frozen' | 'minimal' | 'guided' | 'open';
96
+ steps: number;
97
+ reason: string;
98
+ };
99
+
100
+ export type TsunamiStormSelection = {
101
+ profile: 'frozen' | 'tight' | 'focused' | 'broad';
102
+ signalLimit: number;
103
+ evidenceLimit: number;
104
+ relationLimit: number;
105
+ reason: string;
106
+ };
107
+
108
+ export type TsunamiStormCoverage = {
109
+ mode: 'narrow' | 'focused' | 'broad' | 'full';
110
+ score: number;
111
+ selectedSignals: number;
112
+ totalSignals: number;
113
+ selectedEvidence: number;
114
+ totalEvidence: number;
115
+ selectedRelations: number;
116
+ totalRelations: number;
117
+ reason: string;
118
+ };
119
+
120
+ export type TsunamiStormSaturation = {
121
+ level: 'clear' | 'near_limit' | 'saturated';
122
+ signalHitLimit: boolean;
123
+ evidenceHitLimit: boolean;
124
+ relationHitLimit: boolean;
125
+ hitLanes: Array<'signals' | 'evidence' | 'relations'>;
126
+ reason: string;
127
+ };
128
+
129
+ export type TsunamiStormIntake = {
130
+ mode: 'hold' | 'rebalance' | 'widen' | 'steady';
131
+ target: 'signals' | 'evidence' | 'relations' | 'balanced';
132
+ nextSignalLimit: number;
133
+ nextEvidenceLimit: number;
134
+ nextRelationLimit: number;
135
+ reason: string;
136
+ };
137
+
138
+ export type TsunamiStormCenter = {
139
+ projectDir: string;
140
+ projectNode: string;
141
+ query: string;
142
+ focusQuery: string;
143
+ refreshedGraph: boolean;
144
+ syncSummary?: TsunamiRuntimeGraphSyncSummary | null;
145
+ focus: {
146
+ title: string;
147
+ status?: string;
148
+ summary?: string;
149
+ nextStep?: string;
150
+ featureId?: string;
151
+ };
152
+ flow: {
153
+ basin: string;
154
+ current: string;
155
+ confidence: number;
156
+ };
157
+ supportingBasins: Array<{
158
+ basin: string;
159
+ energy: number;
160
+ drivers: string[];
161
+ }>;
162
+ thread: ProjectTaskThread | null;
163
+ handoff: ProjectHandoffRecord | null;
164
+ recovery: DurableRecoveryRecord | null;
165
+ anchors: Array<{
166
+ pageId: string;
167
+ title: string;
168
+ summary: string;
169
+ confidence: number;
170
+ tags: string[];
171
+ }>;
172
+ evidence: ReturnType<typeof queryProjectWiki>['evidence'];
173
+ graph: {
174
+ project: Record<string, unknown>[];
175
+ thread: Record<string, unknown>[];
176
+ handoff: Record<string, unknown>[];
177
+ anchor: Record<string, unknown>[];
178
+ recovery: Record<string, unknown>[];
179
+ };
180
+ issues: ReturnType<typeof auditMemoryFabric>['issues'];
181
+ repairSuggestions: ReturnType<typeof auditMemoryFabric>['repairSuggestions'];
182
+ currents: TsunamiStormCenterCurrent[];
183
+ currentMix: TsunamiStormCenterCurrentMix[];
184
+ stormMode?: TsunamiStormCenterStormMode;
185
+ stormPressure?: TsunamiStormPressure;
186
+ stormDirective?: TsunamiStormDirective;
187
+ stormAction?: TsunamiStormAction;
188
+ stormReadiness?: TsunamiStormReadiness;
189
+ stormBoundary?: TsunamiStormBoundary;
190
+ stormHorizon?: TsunamiStormHorizon;
191
+ stormConfidence?: TsunamiStormConfidence;
192
+ stormGate?: TsunamiStormGate;
193
+ stormBudget?: TsunamiStormBudget;
194
+ stormSelection?: TsunamiStormSelection;
195
+ stormCoverage?: TsunamiStormCoverage;
196
+ stormSaturation?: TsunamiStormSaturation;
197
+ stormIntake?: TsunamiStormIntake;
198
+ topRepair?: {
199
+ title: string;
200
+ priority?: string;
201
+ detail?: string;
202
+ };
203
+ topIssue?: {
204
+ code: string;
205
+ severity?: string;
206
+ detail?: string;
207
+ };
208
+ metrics: {
209
+ issueCount: number;
210
+ repairCount: number;
211
+ evidenceCount: number;
212
+ anchorCount: number;
213
+ graphEdges: number;
214
+ recoveryDepth: number;
215
+ };
216
+ };