groundswell 0.0.1

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 (120) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.claude/system_prompts/task-breakdown.md +100 -0
  3. package/PRPs/001-hierarchical-workflow-engine.md +2438 -0
  4. package/PRPs/PRDs/001-hierarchical-workflow-engine.md +543 -0
  5. package/PRPs/PRDs/002-agent-prompt.md +390 -0
  6. package/PRPs/PRDs/003-agent-prompt.md +943 -0
  7. package/PRPs/PRDs/004-agent-prompt.md +1136 -0
  8. package/PRPs/PRDs/tasks-001.json +492 -0
  9. package/PRPs/README.md +83 -0
  10. package/PRPs/templates/prp_base.md +222 -0
  11. package/README.md +218 -0
  12. package/docs/agent.md +422 -0
  13. package/docs/prompt.md +419 -0
  14. package/docs/workflow.md +600 -0
  15. package/examples/README.md +244 -0
  16. package/examples/examples/01-basic-workflow.ts +100 -0
  17. package/examples/examples/02-decorator-options.ts +217 -0
  18. package/examples/examples/03-parent-child.ts +241 -0
  19. package/examples/examples/04-observers-debugger.ts +340 -0
  20. package/examples/examples/05-error-handling.ts +387 -0
  21. package/examples/examples/06-concurrent-tasks.ts +352 -0
  22. package/examples/examples/07-agent-loops.ts +432 -0
  23. package/examples/examples/08-sdk-features.ts +667 -0
  24. package/examples/examples/09-reflection.ts +573 -0
  25. package/examples/examples/10-introspection.ts +550 -0
  26. package/examples/index.ts +143 -0
  27. package/examples/utils/helpers.ts +57 -0
  28. package/llms_full.txt +5890 -0
  29. package/package.json +63 -0
  30. package/plan/P1P2/PRP.md +527 -0
  31. package/plan/P1P2/research/LRU_CACHE_BEST_PRACTICES.md +1929 -0
  32. package/plan/P1P2/research/LRU_CACHE_CODE_PATTERNS.md +857 -0
  33. package/plan/P1P2/research/LRU_CACHE_INTEGRATION_GUIDE.md +738 -0
  34. package/plan/P1P2/research/LRU_CACHE_RESEARCH_INDEX.md +424 -0
  35. package/plan/P1P2/research/REFLECTION_INDEX.md +291 -0
  36. package/plan/P1P2/research/REFLECTION_RESEARCH_REPORT.md +1342 -0
  37. package/plan/P1P2/research/RESEARCH_SUMMARY.md +342 -0
  38. package/plan/P1P2/research/anthropic-sdk.md +174 -0
  39. package/plan/P1P2/research/async-local-storage.md +200 -0
  40. package/plan/P1P2/research/reflection-code-patterns.md +1205 -0
  41. package/plan/P1P2/research/reflection-decision-matrix.md +421 -0
  42. package/plan/P1P2/research/reflection-implementation-guide.md +1341 -0
  43. package/plan/P1P2/research/reflection-integration-guide.md +834 -0
  44. package/plan/P1P2/research/reflection-patterns.md +1468 -0
  45. package/plan/P1P2/research/reflection-quick-reference.md +558 -0
  46. package/plan/P1P2/research/zod-schema.md +152 -0
  47. package/plan/P3P4/PRP.md +1388 -0
  48. package/plan/P3P4/research/caching-lru.md +116 -0
  49. package/plan/P3P4/research/introspection-tools.md +177 -0
  50. package/plan/P3P4/research/reflection-patterns.md +117 -0
  51. package/plan/P4P5/PRP.md +1136 -0
  52. package/plan/P4P5/research/RESEARCH_SUMMARY.md +151 -0
  53. package/plan/architecture/external_deps.md +358 -0
  54. package/plan/architecture/system_context.md +242 -0
  55. package/plan/backlog.json +867 -0
  56. package/plan/research/INTROSPECTION_RESEARCH_SUMMARY.md +378 -0
  57. package/plan/research/README-INTROSPECTION.md +352 -0
  58. package/plan/research/agent-introspection-patterns.md +1085 -0
  59. package/plan/research/introspection-security-guide.md +928 -0
  60. package/plan/research/introspection-tool-examples.md +875 -0
  61. package/scripts/generate-llms-full.ts +206 -0
  62. package/src/__tests__/integration/agent-workflow.test.ts +256 -0
  63. package/src/__tests__/integration/tree-mirroring.test.ts +114 -0
  64. package/src/__tests__/unit/agent.test.ts +169 -0
  65. package/src/__tests__/unit/cache-key.test.ts +182 -0
  66. package/src/__tests__/unit/cache.test.ts +172 -0
  67. package/src/__tests__/unit/context.test.ts +138 -0
  68. package/src/__tests__/unit/decorators.test.ts +100 -0
  69. package/src/__tests__/unit/introspection-tools.test.ts +277 -0
  70. package/src/__tests__/unit/prompt.test.ts +135 -0
  71. package/src/__tests__/unit/reflection.test.ts +210 -0
  72. package/src/__tests__/unit/tree-debugger.test.ts +85 -0
  73. package/src/__tests__/unit/workflow.test.ts +81 -0
  74. package/src/cache/cache-key.ts +244 -0
  75. package/src/cache/cache.ts +236 -0
  76. package/src/cache/index.ts +8 -0
  77. package/src/core/agent.ts +573 -0
  78. package/src/core/context.ts +119 -0
  79. package/src/core/event-tree.ts +260 -0
  80. package/src/core/factory.ts +123 -0
  81. package/src/core/index.ts +17 -0
  82. package/src/core/logger.ts +87 -0
  83. package/src/core/mcp-handler.ts +184 -0
  84. package/src/core/prompt.ts +150 -0
  85. package/src/core/workflow-context.ts +349 -0
  86. package/src/core/workflow.ts +302 -0
  87. package/src/debugger/index.ts +1 -0
  88. package/src/debugger/tree-debugger.ts +210 -0
  89. package/src/decorators/index.ts +3 -0
  90. package/src/decorators/observed-state.ts +95 -0
  91. package/src/decorators/step.ts +139 -0
  92. package/src/decorators/task.ts +96 -0
  93. package/src/examples/index.ts +2 -0
  94. package/src/examples/tdd-orchestrator.ts +65 -0
  95. package/src/examples/test-cycle-workflow.ts +64 -0
  96. package/src/index.ts +140 -0
  97. package/src/reflection/index.ts +5 -0
  98. package/src/reflection/reflection.ts +407 -0
  99. package/src/tools/index.ts +36 -0
  100. package/src/tools/introspection.ts +464 -0
  101. package/src/types/agent.ts +90 -0
  102. package/src/types/decorators.ts +25 -0
  103. package/src/types/error-strategy.ts +13 -0
  104. package/src/types/error.ts +20 -0
  105. package/src/types/events.ts +74 -0
  106. package/src/types/index.ts +55 -0
  107. package/src/types/logging.ts +24 -0
  108. package/src/types/observer.ts +18 -0
  109. package/src/types/prompt.ts +40 -0
  110. package/src/types/reflection.ts +117 -0
  111. package/src/types/sdk-primitives.ts +128 -0
  112. package/src/types/snapshot.ts +14 -0
  113. package/src/types/workflow-context.ts +163 -0
  114. package/src/types/workflow.ts +37 -0
  115. package/src/utils/id.ts +11 -0
  116. package/src/utils/index.ts +3 -0
  117. package/src/utils/observable.ts +77 -0
  118. package/tasks.json +0 -0
  119. package/tsconfig.json +22 -0
  120. package/vitest.config.ts +16 -0
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Workflow, Step, Task, ObservedState, getObservedState, WorkflowEvent, WorkflowObserver } from '../../index.js';
3
+
4
+ describe('@Step decorator', () => {
5
+ class StepTestWorkflow extends Workflow {
6
+ stepCalled = false;
7
+
8
+ @Step({ trackTiming: true })
9
+ async myStep(): Promise<string> {
10
+ this.stepCalled = true;
11
+ return 'step result';
12
+ }
13
+
14
+ async run(): Promise<void> {
15
+ await this.myStep();
16
+ }
17
+ }
18
+
19
+ it('should execute the original method', async () => {
20
+ const wf = new StepTestWorkflow();
21
+ await wf.run();
22
+ expect(wf.stepCalled).toBe(true);
23
+ });
24
+
25
+ it('should emit stepStart and stepEnd events', async () => {
26
+ const wf = new StepTestWorkflow();
27
+ const events: WorkflowEvent[] = [];
28
+
29
+ wf.addObserver({
30
+ onLog: () => {},
31
+ onEvent: (e) => events.push(e),
32
+ onStateUpdated: () => {},
33
+ onTreeChanged: () => {},
34
+ });
35
+
36
+ await wf.run();
37
+
38
+ const startEvent = events.find((e) => e.type === 'stepStart');
39
+ const endEvent = events.find((e) => e.type === 'stepEnd');
40
+
41
+ expect(startEvent).toBeDefined();
42
+ expect(endEvent).toBeDefined();
43
+ if (endEvent?.type === 'stepEnd') {
44
+ expect(endEvent.duration).toBeGreaterThanOrEqual(0);
45
+ }
46
+ });
47
+
48
+ it('should wrap errors in WorkflowError', async () => {
49
+ class FailingWorkflow extends Workflow {
50
+ @Step()
51
+ async failingStep(): Promise<void> {
52
+ throw new Error('Step failed');
53
+ }
54
+
55
+ async run(): Promise<void> {
56
+ await this.failingStep();
57
+ }
58
+ }
59
+
60
+ const wf = new FailingWorkflow();
61
+
62
+ await expect(wf.run()).rejects.toMatchObject({
63
+ message: 'Step failed',
64
+ workflowId: wf.id,
65
+ });
66
+ });
67
+ });
68
+
69
+ describe('@ObservedState decorator', () => {
70
+ class StateTestWorkflow extends Workflow {
71
+ @ObservedState()
72
+ publicField: string = 'public';
73
+
74
+ @ObservedState({ redact: true })
75
+ secretField: string = 'secret';
76
+
77
+ @ObservedState({ hidden: true })
78
+ hiddenField: string = 'hidden';
79
+
80
+ async run(): Promise<void> {}
81
+ }
82
+
83
+ it('should include public fields in snapshot', () => {
84
+ const wf = new StateTestWorkflow();
85
+ const state = getObservedState(wf);
86
+ expect(state.publicField).toBe('public');
87
+ });
88
+
89
+ it('should redact secret fields', () => {
90
+ const wf = new StateTestWorkflow();
91
+ const state = getObservedState(wf);
92
+ expect(state.secretField).toBe('***');
93
+ });
94
+
95
+ it('should exclude hidden fields', () => {
96
+ const wf = new StateTestWorkflow();
97
+ const state = getObservedState(wf);
98
+ expect('hiddenField' in state).toBe(false);
99
+ });
100
+ });
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Unit tests for introspection tools
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import {
7
+ INTROSPECTION_TOOLS,
8
+ handleInspectCurrentNode,
9
+ handleReadAncestorChain,
10
+ handleListSiblingsChildren,
11
+ handleInspectPriorOutputs,
12
+ handleInspectCacheStatus,
13
+ handleRequestSpawnWorkflow,
14
+ executeIntrospectionTool,
15
+ } from '../../tools/introspection.js';
16
+ import { runInContext, type AgentExecutionContext } from '../../core/context.js';
17
+ import type { WorkflowNode } from '../../types/index.js';
18
+ import { defaultCache } from '../../cache/cache.js';
19
+
20
+ describe('Introspection Tools', () => {
21
+ describe('INTROSPECTION_TOOLS', () => {
22
+ it('should export 6 tools', () => {
23
+ expect(INTROSPECTION_TOOLS).toHaveLength(6);
24
+ });
25
+
26
+ it('should have valid tool definitions', () => {
27
+ for (const tool of INTROSPECTION_TOOLS) {
28
+ expect(tool.name).toBeDefined();
29
+ expect(tool.description).toBeDefined();
30
+ expect(tool.input_schema).toBeDefined();
31
+ expect(tool.input_schema.type).toBe('object');
32
+ }
33
+ });
34
+
35
+ it('should include all expected tool names', () => {
36
+ const names = INTROSPECTION_TOOLS.map((t) => t.name);
37
+ expect(names).toContain('inspect_current_node');
38
+ expect(names).toContain('read_ancestor_chain');
39
+ expect(names).toContain('list_siblings_children');
40
+ expect(names).toContain('inspect_prior_outputs');
41
+ expect(names).toContain('inspect_cache_status');
42
+ expect(names).toContain('request_spawn_workflow');
43
+ });
44
+ });
45
+
46
+ describe('handlers', () => {
47
+ const createTestNode = (
48
+ id: string,
49
+ name: string,
50
+ parent: WorkflowNode | null = null
51
+ ): WorkflowNode => ({
52
+ id,
53
+ name,
54
+ parent,
55
+ children: [],
56
+ status: 'running',
57
+ logs: [],
58
+ events: [],
59
+ stateSnapshot: null,
60
+ });
61
+
62
+ const createContext = (node: WorkflowNode): AgentExecutionContext => ({
63
+ workflowNode: node,
64
+ emitEvent: () => {},
65
+ workflowId: 'test-workflow',
66
+ });
67
+
68
+ describe('handleInspectCurrentNode', () => {
69
+ it('should throw when not in workflow context', async () => {
70
+ await expect(handleInspectCurrentNode()).rejects.toThrow(
71
+ 'Not in workflow context'
72
+ );
73
+ });
74
+
75
+ it('should return current node info', async () => {
76
+ const parent = createTestNode('parent-1', 'Parent');
77
+ const node = createTestNode('child-1', 'Child', parent);
78
+ parent.children.push(node);
79
+
80
+ const result = await runInContext(createContext(node), () =>
81
+ handleInspectCurrentNode()
82
+ );
83
+
84
+ expect(result.id).toBe('child-1');
85
+ expect(result.name).toBe('Child');
86
+ expect(result.status).toBe('running');
87
+ expect(result.parentId).toBe('parent-1');
88
+ expect(result.parentName).toBe('Parent');
89
+ expect(result.depth).toBe(1);
90
+ });
91
+
92
+ it('should handle root node (no parent)', async () => {
93
+ const node = createTestNode('root-1', 'Root');
94
+
95
+ const result = await runInContext(createContext(node), () =>
96
+ handleInspectCurrentNode()
97
+ );
98
+
99
+ expect(result.parentId).toBeUndefined();
100
+ expect(result.depth).toBe(0);
101
+ });
102
+ });
103
+
104
+ describe('handleReadAncestorChain', () => {
105
+ it('should throw when not in workflow context', async () => {
106
+ await expect(handleReadAncestorChain({})).rejects.toThrow(
107
+ 'Not in workflow context'
108
+ );
109
+ });
110
+
111
+ it('should return ancestor chain', async () => {
112
+ const root = createTestNode('root-1', 'Root');
113
+ const parent = createTestNode('parent-1', 'Parent', root);
114
+ root.children.push(parent);
115
+ const child = createTestNode('child-1', 'Child', parent);
116
+ parent.children.push(child);
117
+
118
+ const result = await runInContext(createContext(child), () =>
119
+ handleReadAncestorChain({})
120
+ );
121
+
122
+ expect(result.ancestors).toHaveLength(2);
123
+ expect(result.ancestors[0].id).toBe('parent-1');
124
+ expect(result.ancestors[1].id).toBe('root-1');
125
+ expect(result.totalDepth).toBe(2);
126
+ });
127
+
128
+ it('should respect maxDepth limit', async () => {
129
+ const root = createTestNode('root-1', 'Root');
130
+ const parent = createTestNode('parent-1', 'Parent', root);
131
+ root.children.push(parent);
132
+ const child = createTestNode('child-1', 'Child', parent);
133
+ parent.children.push(child);
134
+
135
+ const result = await runInContext(createContext(child), () =>
136
+ handleReadAncestorChain({ maxDepth: 1 })
137
+ );
138
+
139
+ expect(result.ancestors).toHaveLength(1);
140
+ expect(result.ancestors[0].id).toBe('parent-1');
141
+ });
142
+ });
143
+
144
+ describe('handleListSiblingsChildren', () => {
145
+ it('should throw when not in workflow context', async () => {
146
+ await expect(
147
+ handleListSiblingsChildren({ type: 'children' })
148
+ ).rejects.toThrow('Not in workflow context');
149
+ });
150
+
151
+ it('should return children', async () => {
152
+ const parent = createTestNode('parent-1', 'Parent');
153
+ const child1 = createTestNode('child-1', 'Child 1', parent);
154
+ const child2 = createTestNode('child-2', 'Child 2', parent);
155
+ parent.children.push(child1, child2);
156
+
157
+ const result = await runInContext(createContext(parent), () =>
158
+ handleListSiblingsChildren({ type: 'children' })
159
+ );
160
+
161
+ expect(result.type).toBe('children');
162
+ expect(result.nodes).toHaveLength(2);
163
+ });
164
+
165
+ it('should return siblings (excluding self)', async () => {
166
+ const parent = createTestNode('parent-1', 'Parent');
167
+ const child1 = createTestNode('child-1', 'Child 1', parent);
168
+ const child2 = createTestNode('child-2', 'Child 2', parent);
169
+ parent.children.push(child1, child2);
170
+
171
+ const result = await runInContext(createContext(child1), () =>
172
+ handleListSiblingsChildren({ type: 'siblings' })
173
+ );
174
+
175
+ expect(result.type).toBe('siblings');
176
+ expect(result.nodes).toHaveLength(1);
177
+ expect(result.nodes[0].id).toBe('child-2');
178
+ });
179
+ });
180
+
181
+ describe('handleInspectPriorOutputs', () => {
182
+ it('should throw when not in workflow context', async () => {
183
+ await expect(handleInspectPriorOutputs({})).rejects.toThrow(
184
+ 'Not in workflow context'
185
+ );
186
+ });
187
+
188
+ it('should return completed sibling outputs', async () => {
189
+ const parent = createTestNode('parent-1', 'Parent');
190
+ const completed = createTestNode('completed-1', 'Completed', parent);
191
+ completed.status = 'completed';
192
+ // Add a valid workflow event for testing
193
+ completed.events.push({
194
+ type: 'stepEnd',
195
+ node: completed,
196
+ step: 'test-step',
197
+ duration: 100,
198
+ } as never); // Cast to avoid strict type checking in tests
199
+ const current = createTestNode('current-1', 'Current', parent);
200
+ parent.children.push(completed, current);
201
+
202
+ const result = await runInContext(createContext(current), () =>
203
+ handleInspectPriorOutputs({})
204
+ );
205
+
206
+ expect(result).toHaveLength(1);
207
+ expect(result[0].nodeId).toBe('completed-1');
208
+ expect(result[0].status).toBe('completed');
209
+ });
210
+ });
211
+
212
+ describe('handleInspectCacheStatus', () => {
213
+ beforeEach(async () => {
214
+ await defaultCache.clear();
215
+ });
216
+
217
+ it('should return false for missing cache key', async () => {
218
+ const result = await handleInspectCacheStatus({
219
+ promptHash: 'nonexistent-key',
220
+ });
221
+
222
+ expect(result.exists).toBe(false);
223
+ expect(result.key).toBe('nonexistent-key');
224
+ });
225
+
226
+ it('should return true for existing cache key', async () => {
227
+ await defaultCache.set('test-key', { data: 'test' });
228
+
229
+ const result = await handleInspectCacheStatus({ promptHash: 'test-key' });
230
+
231
+ expect(result.exists).toBe(true);
232
+ });
233
+ });
234
+
235
+ describe('handleRequestSpawnWorkflow', () => {
236
+ it('should throw when not in workflow context', async () => {
237
+ await expect(
238
+ handleRequestSpawnWorkflow({ name: 'Test', description: 'Test workflow' })
239
+ ).rejects.toThrow('Not in workflow context');
240
+ });
241
+
242
+ it('should return spawn request', async () => {
243
+ const node = createTestNode('node-1', 'Node');
244
+
245
+ const result = await runInContext(createContext(node), () =>
246
+ handleRequestSpawnWorkflow({
247
+ name: 'NewWorkflow',
248
+ description: 'A new workflow',
249
+ })
250
+ );
251
+
252
+ expect(result.name).toBe('NewWorkflow');
253
+ expect(result.description).toBe('A new workflow');
254
+ expect(result.requestId).toMatch(/^spawn-/);
255
+ expect(result.status).toBe('pending');
256
+ });
257
+ });
258
+ });
259
+
260
+ describe('executeIntrospectionTool', () => {
261
+ it('should execute tool by name', async () => {
262
+ await defaultCache.set('cache-test', 'value');
263
+
264
+ const result = await executeIntrospectionTool('inspect_cache_status', {
265
+ promptHash: 'cache-test',
266
+ });
267
+
268
+ expect(result).toMatchObject({ exists: true, key: 'cache-test' });
269
+ });
270
+
271
+ it('should throw for unknown tool', async () => {
272
+ await expect(
273
+ executeIntrospectionTool('unknown_tool', {})
274
+ ).rejects.toThrow('Unknown introspection tool');
275
+ });
276
+ });
277
+ });
@@ -0,0 +1,135 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { z } from 'zod';
3
+ import { Prompt } from '../../core/prompt.js';
4
+
5
+ describe('Prompt', () => {
6
+ it('should create with unique id', () => {
7
+ const p1 = new Prompt({
8
+ user: 'Test',
9
+ responseFormat: z.object({ message: z.string() }),
10
+ });
11
+ const p2 = new Prompt({
12
+ user: 'Test',
13
+ responseFormat: z.object({ message: z.string() }),
14
+ });
15
+ expect(p1.id).not.toBe(p2.id);
16
+ });
17
+
18
+ it('should store user message and data', () => {
19
+ const prompt = new Prompt({
20
+ user: 'Hello world',
21
+ data: { key: 'value' },
22
+ responseFormat: z.object({ result: z.string() }),
23
+ });
24
+
25
+ expect(prompt.user).toBe('Hello world');
26
+ expect(prompt.data).toEqual({ key: 'value' });
27
+ });
28
+
29
+ it('should validate response successfully', () => {
30
+ const schema = z.object({
31
+ name: z.string(),
32
+ age: z.number(),
33
+ });
34
+
35
+ const prompt = new Prompt({
36
+ user: 'Get person',
37
+ responseFormat: schema,
38
+ });
39
+
40
+ const result = prompt.validateResponse({ name: 'John', age: 30 });
41
+ expect(result).toEqual({ name: 'John', age: 30 });
42
+ });
43
+
44
+ it('should throw on invalid response', () => {
45
+ const schema = z.object({
46
+ name: z.string(),
47
+ age: z.number(),
48
+ });
49
+
50
+ const prompt = new Prompt({
51
+ user: 'Get person',
52
+ responseFormat: schema,
53
+ });
54
+
55
+ expect(() => prompt.validateResponse({ name: 'John' })).toThrow();
56
+ });
57
+
58
+ it('should safely validate response', () => {
59
+ const schema = z.object({ value: z.number() });
60
+ const prompt = new Prompt({
61
+ user: 'Test',
62
+ responseFormat: schema,
63
+ });
64
+
65
+ const success = prompt.safeValidateResponse({ value: 42 });
66
+ expect(success.success).toBe(true);
67
+ if (success.success) {
68
+ expect(success.data).toEqual({ value: 42 });
69
+ }
70
+
71
+ const failure = prompt.safeValidateResponse({ value: 'not a number' });
72
+ expect(failure.success).toBe(false);
73
+ });
74
+
75
+ it('should build user message without data', () => {
76
+ const prompt = new Prompt({
77
+ user: 'Simple message',
78
+ responseFormat: z.string(),
79
+ });
80
+
81
+ expect(prompt.buildUserMessage()).toBe('Simple message');
82
+ });
83
+
84
+ it('should build user message with data', () => {
85
+ const prompt = new Prompt({
86
+ user: 'Message with data',
87
+ data: { items: ['a', 'b', 'c'] },
88
+ responseFormat: z.string(),
89
+ });
90
+
91
+ const message = prompt.buildUserMessage();
92
+ expect(message).toContain('Message with data');
93
+ expect(message).toContain('<items>');
94
+ expect(message).toContain('</items>');
95
+ });
96
+
97
+ it('should create new prompt with updated data', () => {
98
+ const original = new Prompt({
99
+ user: 'Test',
100
+ data: { a: 1 },
101
+ responseFormat: z.string(),
102
+ });
103
+
104
+ const updated = original.withData({ b: 2 });
105
+
106
+ expect(original.data).toEqual({ a: 1 });
107
+ expect(updated.data).toEqual({ a: 1, b: 2 });
108
+ expect(original.id).not.toBe(updated.id);
109
+ });
110
+
111
+ it('should store override fields', () => {
112
+ const prompt = new Prompt({
113
+ user: 'Test',
114
+ responseFormat: z.string(),
115
+ system: 'Custom system prompt',
116
+ tools: [{ name: 'test', description: 'Test tool', input_schema: { type: 'object', properties: {} } }],
117
+ enableReflection: true,
118
+ });
119
+
120
+ expect(prompt.systemOverride).toBe('Custom system prompt');
121
+ expect(prompt.toolsOverride).toHaveLength(1);
122
+ expect(prompt.enableReflection).toBe(true);
123
+ });
124
+
125
+ it('should be immutable', () => {
126
+ const prompt = new Prompt({
127
+ user: 'Test',
128
+ data: { key: 'value' },
129
+ responseFormat: z.string(),
130
+ });
131
+
132
+ expect(Object.isFrozen(prompt)).toBe(true);
133
+ expect(Object.isFrozen(prompt.data)).toBe(true);
134
+ });
135
+ });