obsidian-accomplishments-mcp 0.1.10 → 0.1.12

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 (269) hide show
  1. package/README.md +156 -184
  2. package/dist/index.js +207 -38
  3. package/dist/index.js.map +1 -1
  4. package/dist/integration.test.d.ts +8 -0
  5. package/dist/integration.test.d.ts.map +1 -0
  6. package/dist/integration.test.js +979 -0
  7. package/dist/integration.test.js.map +1 -0
  8. package/dist/models/types.d.ts +1 -2
  9. package/dist/models/types.d.ts.map +1 -1
  10. package/dist/models/types.js.map +1 -1
  11. package/dist/models/v2-types.d.ts +460 -0
  12. package/dist/models/v2-types.d.ts.map +1 -0
  13. package/dist/models/v2-types.js +137 -0
  14. package/dist/models/v2-types.js.map +1 -0
  15. package/dist/models/v2-types.test.d.ts +5 -0
  16. package/dist/models/v2-types.test.d.ts.map +1 -0
  17. package/dist/models/v2-types.test.js +133 -0
  18. package/dist/models/v2-types.test.js.map +1 -0
  19. package/dist/parsers/canvas-parser.d.ts +1 -1
  20. package/dist/parsers/canvas-parser.d.ts.map +1 -1
  21. package/dist/parsers/canvas-parser.js +1 -1
  22. package/dist/parsers/canvas-parser.js.map +1 -1
  23. package/dist/parsers/markdown-parser.js +9 -9
  24. package/dist/parsers/markdown-parser.js.map +1 -1
  25. package/dist/services/v2/archive-manager.d.ts +96 -0
  26. package/dist/services/v2/archive-manager.d.ts.map +1 -0
  27. package/dist/services/v2/archive-manager.js +281 -0
  28. package/dist/services/v2/archive-manager.js.map +1 -0
  29. package/dist/services/v2/canvas-manager.d.ts +155 -0
  30. package/dist/services/v2/canvas-manager.d.ts.map +1 -0
  31. package/dist/services/v2/canvas-manager.js +540 -0
  32. package/dist/services/v2/canvas-manager.js.map +1 -0
  33. package/dist/services/v2/canvas-manager.test.d.ts +5 -0
  34. package/dist/services/v2/canvas-manager.test.d.ts.map +1 -0
  35. package/dist/services/v2/canvas-manager.test.js +327 -0
  36. package/dist/services/v2/canvas-manager.test.js.map +1 -0
  37. package/dist/services/v2/cascade-manager.d.ts +54 -0
  38. package/dist/services/v2/cascade-manager.d.ts.map +1 -0
  39. package/dist/services/v2/cascade-manager.js +220 -0
  40. package/dist/services/v2/cascade-manager.js.map +1 -0
  41. package/dist/services/v2/cycle-detector.d.ts +76 -0
  42. package/dist/services/v2/cycle-detector.d.ts.map +1 -0
  43. package/dist/services/v2/cycle-detector.js +183 -0
  44. package/dist/services/v2/cycle-detector.js.map +1 -0
  45. package/dist/services/v2/cycle-detector.test.d.ts +7 -0
  46. package/dist/services/v2/cycle-detector.test.d.ts.map +1 -0
  47. package/dist/services/v2/cycle-detector.test.js +125 -0
  48. package/dist/services/v2/cycle-detector.test.js.map +1 -0
  49. package/dist/services/v2/entity-parser.d.ts +54 -0
  50. package/dist/services/v2/entity-parser.d.ts.map +1 -0
  51. package/dist/services/v2/entity-parser.js +418 -0
  52. package/dist/services/v2/entity-parser.js.map +1 -0
  53. package/dist/services/v2/entity-parser.test.d.ts +5 -0
  54. package/dist/services/v2/entity-parser.test.d.ts.map +1 -0
  55. package/dist/services/v2/entity-parser.test.js +637 -0
  56. package/dist/services/v2/entity-parser.test.js.map +1 -0
  57. package/dist/services/v2/entity-serializer.d.ts +94 -0
  58. package/dist/services/v2/entity-serializer.d.ts.map +1 -0
  59. package/dist/services/v2/entity-serializer.js +583 -0
  60. package/dist/services/v2/entity-serializer.js.map +1 -0
  61. package/dist/services/v2/entity-serializer.test.d.ts +5 -0
  62. package/dist/services/v2/entity-serializer.test.d.ts.map +1 -0
  63. package/dist/services/v2/entity-serializer.test.js +241 -0
  64. package/dist/services/v2/entity-serializer.test.js.map +1 -0
  65. package/dist/services/v2/entity-validator.d.ts +65 -0
  66. package/dist/services/v2/entity-validator.d.ts.map +1 -0
  67. package/dist/services/v2/entity-validator.js +573 -0
  68. package/dist/services/v2/entity-validator.js.map +1 -0
  69. package/dist/services/v2/entity-validator.test.d.ts +5 -0
  70. package/dist/services/v2/entity-validator.test.d.ts.map +1 -0
  71. package/dist/services/v2/entity-validator.test.js +519 -0
  72. package/dist/services/v2/entity-validator.test.js.map +1 -0
  73. package/dist/services/v2/file-manager.d.ts +73 -0
  74. package/dist/services/v2/file-manager.d.ts.map +1 -0
  75. package/dist/services/v2/file-manager.js +310 -0
  76. package/dist/services/v2/file-manager.js.map +1 -0
  77. package/dist/services/v2/file-manager.test.d.ts +5 -0
  78. package/dist/services/v2/file-manager.test.d.ts.map +1 -0
  79. package/dist/services/v2/file-manager.test.js +339 -0
  80. package/dist/services/v2/file-manager.test.js.map +1 -0
  81. package/dist/services/v2/index-manager.d.ts +68 -0
  82. package/dist/services/v2/index-manager.d.ts.map +1 -0
  83. package/dist/services/v2/index-manager.js +228 -0
  84. package/dist/services/v2/index-manager.js.map +1 -0
  85. package/dist/services/v2/index-manager.test.d.ts +5 -0
  86. package/dist/services/v2/index-manager.test.d.ts.map +1 -0
  87. package/dist/services/v2/index-manager.test.js +386 -0
  88. package/dist/services/v2/index-manager.test.js.map +1 -0
  89. package/dist/services/v2/index-service.d.ts +82 -0
  90. package/dist/services/v2/index-service.d.ts.map +1 -0
  91. package/dist/services/v2/index-service.js +274 -0
  92. package/dist/services/v2/index-service.js.map +1 -0
  93. package/dist/services/v2/index-service.test.d.ts +5 -0
  94. package/dist/services/v2/index-service.test.d.ts.map +1 -0
  95. package/dist/services/v2/index-service.test.js +117 -0
  96. package/dist/services/v2/index-service.test.js.map +1 -0
  97. package/dist/services/v2/lifecycle-manager.d.ts +59 -0
  98. package/dist/services/v2/lifecycle-manager.d.ts.map +1 -0
  99. package/dist/services/v2/lifecycle-manager.js +310 -0
  100. package/dist/services/v2/lifecycle-manager.js.map +1 -0
  101. package/dist/services/v2/lifecycle-manager.test.d.ts +5 -0
  102. package/dist/services/v2/lifecycle-manager.test.d.ts.map +1 -0
  103. package/dist/services/v2/lifecycle-manager.test.js +141 -0
  104. package/dist/services/v2/lifecycle-manager.test.js.map +1 -0
  105. package/dist/services/v2/path-resolver.d.ts +64 -0
  106. package/dist/services/v2/path-resolver.d.ts.map +1 -0
  107. package/dist/services/v2/path-resolver.js +174 -0
  108. package/dist/services/v2/path-resolver.js.map +1 -0
  109. package/dist/services/v2/progress-computer.d.ts +46 -0
  110. package/dist/services/v2/progress-computer.d.ts.map +1 -0
  111. package/dist/services/v2/progress-computer.js +200 -0
  112. package/dist/services/v2/progress-computer.js.map +1 -0
  113. package/dist/services/v2/search-service.d.ts +68 -0
  114. package/dist/services/v2/search-service.d.ts.map +1 -0
  115. package/dist/services/v2/search-service.js +194 -0
  116. package/dist/services/v2/search-service.js.map +1 -0
  117. package/dist/services/v2/transitive-dependency-remover.d.ts +54 -0
  118. package/dist/services/v2/transitive-dependency-remover.d.ts.map +1 -0
  119. package/dist/services/v2/transitive-dependency-remover.js +156 -0
  120. package/dist/services/v2/transitive-dependency-remover.js.map +1 -0
  121. package/dist/services/v2/transitive-dependency-remover.test.d.ts +7 -0
  122. package/dist/services/v2/transitive-dependency-remover.test.d.ts.map +1 -0
  123. package/dist/services/v2/transitive-dependency-remover.test.js +119 -0
  124. package/dist/services/v2/transitive-dependency-remover.test.js.map +1 -0
  125. package/dist/services/v2/v2-runtime.d.ts +374 -0
  126. package/dist/services/v2/v2-runtime.d.ts.map +1 -0
  127. package/dist/services/v2/v2-runtime.js +1908 -0
  128. package/dist/services/v2/v2-runtime.js.map +1 -0
  129. package/dist/services/v2/v2-runtime.test.d.ts +5 -0
  130. package/dist/services/v2/v2-runtime.test.d.ts.map +1 -0
  131. package/dist/services/v2/v2-runtime.test.js +658 -0
  132. package/dist/services/v2/v2-runtime.test.js.map +1 -0
  133. package/dist/services/v2/workstream-normalizer.d.ts +59 -0
  134. package/dist/services/v2/workstream-normalizer.d.ts.map +1 -0
  135. package/dist/services/v2/workstream-normalizer.js +137 -0
  136. package/dist/services/v2/workstream-normalizer.js.map +1 -0
  137. package/dist/services/v2/workstream-normalizer.test.d.ts +7 -0
  138. package/dist/services/v2/workstream-normalizer.test.d.ts.map +1 -0
  139. package/dist/services/v2/workstream-normalizer.test.js +130 -0
  140. package/dist/services/v2/workstream-normalizer.test.js.map +1 -0
  141. package/dist/test-runner.d.ts +4 -1
  142. package/dist/test-runner.d.ts.map +1 -1
  143. package/dist/test-runner.js +44 -249
  144. package/dist/test-runner.js.map +1 -1
  145. package/dist/tools/batch-operations-tools.d.ts +54 -0
  146. package/dist/tools/batch-operations-tools.d.ts.map +1 -0
  147. package/dist/tools/batch-operations-tools.js +370 -0
  148. package/dist/tools/batch-operations-tools.js.map +1 -0
  149. package/dist/tools/decision-document-tools.d.ts +78 -0
  150. package/dist/tools/decision-document-tools.d.ts.map +1 -0
  151. package/dist/tools/decision-document-tools.js +260 -0
  152. package/dist/tools/decision-document-tools.js.map +1 -0
  153. package/dist/tools/entity-management-tools.d.ts +79 -0
  154. package/dist/tools/entity-management-tools.d.ts.map +1 -0
  155. package/dist/tools/entity-management-tools.js +851 -0
  156. package/dist/tools/entity-management-tools.js.map +1 -0
  157. package/dist/tools/entity-management-tools.test.d.ts +5 -0
  158. package/dist/tools/entity-management-tools.test.d.ts.map +1 -0
  159. package/dist/tools/entity-management-tools.test.js +530 -0
  160. package/dist/tools/entity-management-tools.test.js.map +1 -0
  161. package/dist/tools/index.d.ts +15 -331
  162. package/dist/tools/index.d.ts.map +1 -1
  163. package/dist/tools/index.js +510 -47
  164. package/dist/tools/index.js.map +1 -1
  165. package/dist/tools/index.test.d.ts +8 -0
  166. package/dist/tools/index.test.d.ts.map +1 -0
  167. package/dist/tools/index.test.js +429 -0
  168. package/dist/tools/index.test.js.map +1 -0
  169. package/dist/tools/project-understanding-tools.d.ts +75 -0
  170. package/dist/tools/project-understanding-tools.d.ts.map +1 -0
  171. package/dist/tools/project-understanding-tools.js +751 -0
  172. package/dist/tools/project-understanding-tools.js.map +1 -0
  173. package/dist/tools/search-navigation-tools.d.ts +77 -0
  174. package/dist/tools/search-navigation-tools.d.ts.map +1 -0
  175. package/dist/tools/search-navigation-tools.js +379 -0
  176. package/dist/tools/search-navigation-tools.js.map +1 -0
  177. package/dist/tools/tool-types.d.ts +703 -0
  178. package/dist/tools/tool-types.d.ts.map +1 -0
  179. package/dist/tools/tool-types.js +7 -0
  180. package/dist/tools/tool-types.js.map +1 -0
  181. package/dist/utils/config.d.ts +0 -4
  182. package/dist/utils/config.d.ts.map +1 -1
  183. package/dist/utils/config.js +2 -19
  184. package/dist/utils/config.js.map +1 -1
  185. package/package.json +16 -1
  186. package/dist/services/accomplishment-service.d.ts +0 -33
  187. package/dist/services/accomplishment-service.d.ts.map +0 -1
  188. package/dist/services/accomplishment-service.js +0 -296
  189. package/dist/services/accomplishment-service.js.map +0 -1
  190. package/dist/services/canvas-service.d.ts +0 -96
  191. package/dist/services/canvas-service.d.ts.map +0 -1
  192. package/dist/services/canvas-service.js +0 -231
  193. package/dist/services/canvas-service.js.map +0 -1
  194. package/dist/services/context-doc-service.d.ts +0 -70
  195. package/dist/services/context-doc-service.d.ts.map +0 -1
  196. package/dist/services/context-doc-service.js +0 -229
  197. package/dist/services/context-doc-service.js.map +0 -1
  198. package/dist/services/dependency-service.d.ts +0 -22
  199. package/dist/services/dependency-service.d.ts.map +0 -1
  200. package/dist/services/dependency-service.js +0 -99
  201. package/dist/services/dependency-service.js.map +0 -1
  202. package/dist/services/status-indicator-service.d.ts +0 -40
  203. package/dist/services/status-indicator-service.d.ts.map +0 -1
  204. package/dist/services/status-indicator-service.js +0 -173
  205. package/dist/services/status-indicator-service.js.map +0 -1
  206. package/dist/services/task-service.d.ts +0 -32
  207. package/dist/services/task-service.d.ts.map +0 -1
  208. package/dist/services/task-service.js +0 -152
  209. package/dist/services/task-service.js.map +0 -1
  210. package/dist/test-real-vault.d.ts +0 -6
  211. package/dist/test-real-vault.d.ts.map +0 -1
  212. package/dist/test-real-vault.js +0 -30
  213. package/dist/test-real-vault.js.map +0 -1
  214. package/dist/tools/batch-operations.d.ts +0 -246
  215. package/dist/tools/batch-operations.d.ts.map +0 -1
  216. package/dist/tools/batch-operations.js +0 -235
  217. package/dist/tools/batch-operations.js.map +0 -1
  218. package/dist/tools/get-accomplishment.d.ts +0 -42
  219. package/dist/tools/get-accomplishment.d.ts.map +0 -1
  220. package/dist/tools/get-accomplishment.js +0 -93
  221. package/dist/tools/get-accomplishment.js.map +0 -1
  222. package/dist/tools/get-accomplishments-graph.d.ts +0 -26
  223. package/dist/tools/get-accomplishments-graph.d.ts.map +0 -1
  224. package/dist/tools/get-accomplishments-graph.js +0 -137
  225. package/dist/tools/get-accomplishments-graph.js.map +0 -1
  226. package/dist/tools/get-blocked-items.d.ts +0 -15
  227. package/dist/tools/get-blocked-items.d.ts.map +0 -1
  228. package/dist/tools/get-blocked-items.js +0 -73
  229. package/dist/tools/get-blocked-items.js.map +0 -1
  230. package/dist/tools/get-current-work.d.ts +0 -15
  231. package/dist/tools/get-current-work.d.ts.map +0 -1
  232. package/dist/tools/get-current-work.js +0 -68
  233. package/dist/tools/get-current-work.js.map +0 -1
  234. package/dist/tools/get-project-status.d.ts +0 -26
  235. package/dist/tools/get-project-status.d.ts.map +0 -1
  236. package/dist/tools/get-project-status.js +0 -98
  237. package/dist/tools/get-project-status.js.map +0 -1
  238. package/dist/tools/get-ready-to-start.d.ts +0 -15
  239. package/dist/tools/get-ready-to-start.d.ts.map +0 -1
  240. package/dist/tools/get-ready-to-start.js +0 -47
  241. package/dist/tools/get-ready-to-start.js.map +0 -1
  242. package/dist/tools/list-accomplishments.d.ts +0 -42
  243. package/dist/tools/list-accomplishments.d.ts.map +0 -1
  244. package/dist/tools/list-accomplishments.js +0 -40
  245. package/dist/tools/list-accomplishments.js.map +0 -1
  246. package/dist/tools/manage-accomplishment.d.ts +0 -147
  247. package/dist/tools/manage-accomplishment.d.ts.map +0 -1
  248. package/dist/tools/manage-accomplishment.js +0 -153
  249. package/dist/tools/manage-accomplishment.js.map +0 -1
  250. package/dist/tools/manage-dependency.d.ts +0 -41
  251. package/dist/tools/manage-dependency.d.ts.map +0 -1
  252. package/dist/tools/manage-dependency.js +0 -66
  253. package/dist/tools/manage-dependency.js.map +0 -1
  254. package/dist/tools/manage-task.d.ts +0 -119
  255. package/dist/tools/manage-task.d.ts.map +0 -1
  256. package/dist/tools/manage-task.js +0 -126
  257. package/dist/tools/manage-task.js.map +0 -1
  258. package/dist/tools/reconcile-canvas.d.ts +0 -33
  259. package/dist/tools/reconcile-canvas.d.ts.map +0 -1
  260. package/dist/tools/reconcile-canvas.js +0 -41
  261. package/dist/tools/reconcile-canvas.js.map +0 -1
  262. package/dist/tools/set-work-focus.d.ts +0 -48
  263. package/dist/tools/set-work-focus.d.ts.map +0 -1
  264. package/dist/tools/set-work-focus.js +0 -78
  265. package/dist/tools/set-work-focus.js.map +0 -1
  266. package/dist/tools/sync-dependencies.d.ts +0 -33
  267. package/dist/tools/sync-dependencies.d.ts.map +0 -1
  268. package/dist/tools/sync-dependencies.js +0 -144
  269. package/dist/tools/sync-dependencies.js.map +0 -1
@@ -0,0 +1,979 @@
1
+ /**
2
+ * Integration Tests for MCP Tools
3
+ *
4
+ * These tests exercise the full stack: tools → runtime → services
5
+ * Each scenario covers multiple components to maximize coverage efficiently.
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import * as fs from 'fs/promises';
9
+ import * as path from 'path';
10
+ import * as os from 'os';
11
+ import { getV2Runtime, resetV2Runtime } from './services/v2/v2-runtime.js';
12
+ // Tool implementations
13
+ import { createEntity, updateEntity, updateEntityStatus, archiveEntity, restoreFromArchive, } from './tools/entity-management-tools.js';
14
+ import { searchEntities, getEntity, } from './tools/search-navigation-tools.js';
15
+ import { getDecisionHistory, } from './tools/decision-document-tools.js';
16
+ import { batchUpdate, } from './tools/batch-operations-tools.js';
17
+ import { getProjectOverview, getWorkstreamStatus, analyzeProjectState, } from './tools/project-understanding-tools.js';
18
+ // =============================================================================
19
+ // Test Setup
20
+ // =============================================================================
21
+ describe('MCP Integration Tests', () => {
22
+ let tempDir;
23
+ let runtime;
24
+ let config;
25
+ beforeEach(async () => {
26
+ // Reset the runtime singleton to ensure clean state between tests
27
+ resetV2Runtime();
28
+ // Create temp directory structure
29
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-integration-test-'));
30
+ config = {
31
+ vaultPath: tempDir,
32
+ entitiesFolder: 'accomplishments',
33
+ archiveFolder: 'accomplishments/archive',
34
+ canvasFolder: 'accomplishments',
35
+ defaultCanvas: 'canvas.canvas',
36
+ workspaces: {},
37
+ };
38
+ // Create folder structure
39
+ await fs.mkdir(path.join(tempDir, 'accomplishments', 'milestones'), { recursive: true });
40
+ await fs.mkdir(path.join(tempDir, 'accomplishments', 'stories'), { recursive: true });
41
+ await fs.mkdir(path.join(tempDir, 'accomplishments', 'tasks'), { recursive: true });
42
+ await fs.mkdir(path.join(tempDir, 'accomplishments', 'decisions'), { recursive: true });
43
+ await fs.mkdir(path.join(tempDir, 'accomplishments', 'documents'), { recursive: true });
44
+ await fs.mkdir(path.join(tempDir, 'accomplishments', 'archive'), { recursive: true });
45
+ // Get runtime
46
+ runtime = await getV2Runtime(config);
47
+ await runtime.initialize();
48
+ });
49
+ afterEach(async () => {
50
+ await fs.rm(tempDir, { recursive: true, force: true });
51
+ });
52
+ // ===========================================================================
53
+ // Scenario 1: Entity CRUD Workflow
54
+ // ===========================================================================
55
+ describe('Scenario 1: Entity CRUD Workflow', () => {
56
+ it('should create, read, update, and delete a milestone', async () => {
57
+ const deps = runtime.getEntityManagementDeps();
58
+ const searchDeps = runtime.getSearchNavigationDeps();
59
+ // CREATE
60
+ const createResult = await createEntity({
61
+ type: 'milestone',
62
+ data: {
63
+ title: 'Q1 Release',
64
+ workstream: 'engineering',
65
+ objective: 'Deliver Q1 features',
66
+ },
67
+ }, deps);
68
+ expect(createResult.id).toMatch(/^M-\d{3}$/);
69
+ expect(createResult.entity.title).toBe('Q1 Release');
70
+ expect(createResult.entity.status).toBe('Not Started');
71
+ // READ via get_entity
72
+ const fullResult = await getEntity({
73
+ id: createResult.id,
74
+ fields: ['id', 'title', 'status', 'type', 'workstream'],
75
+ }, searchDeps);
76
+ // getEntity returns entity fields directly
77
+ expect(fullResult.id).toBe(createResult.id);
78
+ expect(fullResult.title).toBe('Q1 Release');
79
+ // UPDATE
80
+ const updateResult = await updateEntity({
81
+ id: createResult.id,
82
+ data: {
83
+ title: 'Q1 Release - Updated',
84
+ },
85
+ }, deps);
86
+ expect(updateResult.entity.title).toBe('Q1 Release - Updated');
87
+ expect(updateResult.id).toBe(createResult.id);
88
+ // UPDATE STATUS
89
+ const statusResult = await updateEntityStatus({
90
+ id: createResult.id,
91
+ status: 'In Progress',
92
+ }, deps);
93
+ expect(statusResult.old_status).toBe('Not Started');
94
+ expect(statusResult.new_status).toBe('In Progress');
95
+ // Verify file exists (filename is based on title, not ID)
96
+ const files = await fs.readdir(path.join(tempDir, 'accomplishments', 'milestones'));
97
+ expect(files.length).toBeGreaterThanOrEqual(1);
98
+ // After update, title is "Q1 Release - Updated" which becomes "Q1_Release_-_Updated.md"
99
+ expect(files.some(f => f.includes('Q1_Release'))).toBe(true);
100
+ });
101
+ it('should create a full hierarchy: milestone → story → task', async () => {
102
+ const deps = runtime.getEntityManagementDeps();
103
+ const searchDeps = runtime.getSearchNavigationDeps();
104
+ // Create milestone
105
+ const milestone = await createEntity({
106
+ type: 'milestone',
107
+ data: { title: 'Q1 Release', workstream: 'engineering' },
108
+ }, deps);
109
+ // Create story under milestone
110
+ const story = await createEntity({
111
+ type: 'story',
112
+ data: {
113
+ title: 'User Authentication',
114
+ workstream: 'engineering',
115
+ parent: milestone.id,
116
+ outcome: 'Users can log in securely',
117
+ },
118
+ }, deps);
119
+ // Verify story was created with correct ID
120
+ expect(story.id).toMatch(/^S-\d{3}$/);
121
+ expect(story.entity.title).toBe('User Authentication');
122
+ // Create task under story
123
+ const task = await createEntity({
124
+ type: 'task',
125
+ data: {
126
+ title: 'Implement JWT tokens',
127
+ workstream: 'engineering',
128
+ parent: story.id,
129
+ goal: 'Add JWT authentication',
130
+ },
131
+ }, deps);
132
+ // Verify task was created
133
+ expect(task.id).toMatch(/^T-\d{3}$/);
134
+ expect(task.entity.title).toBe('Implement JWT tokens');
135
+ // Navigate hierarchy - get children using searchEntities
136
+ const navDown = await searchEntities({
137
+ from_id: story.id,
138
+ direction: 'down',
139
+ depth: 2,
140
+ }, searchDeps);
141
+ expect(navDown.results.length).toBe(1);
142
+ expect(navDown.results[0].id).toBe(task.id);
143
+ // Navigate hierarchy - get parent using searchEntities
144
+ const navUp = await searchEntities({
145
+ from_id: story.id,
146
+ direction: 'up',
147
+ depth: 2,
148
+ }, searchDeps);
149
+ expect(navUp.results.length).toBe(1);
150
+ expect(navUp.results[0].id).toBe(milestone.id);
151
+ });
152
+ });
153
+ // ===========================================================================
154
+ // Scenario 2: Search & Navigation
155
+ // ===========================================================================
156
+ describe('Scenario 2: Search & Navigation', () => {
157
+ it('should search entities by query and filters', async () => {
158
+ const deps = runtime.getEntityManagementDeps();
159
+ const searchDeps = runtime.getSearchNavigationDeps();
160
+ // Create multiple entities
161
+ await createEntity({
162
+ type: 'milestone',
163
+ data: { title: 'Authentication System', workstream: 'security' },
164
+ }, deps);
165
+ await createEntity({
166
+ type: 'story',
167
+ data: { title: 'OAuth Integration', workstream: 'security', outcome: 'Support OAuth2 authentication' },
168
+ }, deps);
169
+ await createEntity({
170
+ type: 'task',
171
+ data: { title: 'Database Migration', workstream: 'infrastructure', goal: 'Migrate to PostgreSQL' },
172
+ }, deps);
173
+ // Re-initialize to index new entities
174
+ await runtime.initialize();
175
+ // Search for "authentication"
176
+ const authResults = await searchEntities({
177
+ query: 'authentication',
178
+ limit: 10,
179
+ }, searchDeps);
180
+ expect(authResults.results.length).toBeGreaterThanOrEqual(1);
181
+ // Search with type filter
182
+ const milestoneResults = await searchEntities({
183
+ query: 'system',
184
+ filters: { type: ['milestone'] },
185
+ limit: 10,
186
+ }, searchDeps);
187
+ expect(milestoneResults.results.every(r => r.type === 'milestone')).toBe(true);
188
+ });
189
+ it('should get entity summary and full details', async () => {
190
+ const deps = runtime.getEntityManagementDeps();
191
+ const searchDeps = runtime.getSearchNavigationDeps();
192
+ const milestone = await createEntity({
193
+ type: 'milestone',
194
+ data: {
195
+ title: 'Q2 Goals',
196
+ workstream: 'product',
197
+ objective: 'Complete Q2 deliverables',
198
+ priority: 'High',
199
+ },
200
+ }, deps);
201
+ // Get entity with summary fields
202
+ const summary = await getEntity({ id: milestone.id, fields: ['id', 'title', 'type', 'status'] }, searchDeps);
203
+ expect(summary.id).toBe(milestone.id);
204
+ expect(summary.title).toBe('Q2 Goals');
205
+ expect(summary.type).toBe('milestone');
206
+ // Get entity with all fields
207
+ const full = await getEntity({
208
+ id: milestone.id,
209
+ fields: ['id', 'title', 'type', 'status', 'workstream', 'content'],
210
+ }, searchDeps);
211
+ expect(full.id).toBe(milestone.id);
212
+ // Content may be empty or contain the objective
213
+ expect(full.title).toBe('Q2 Goals');
214
+ });
215
+ });
216
+ // ===========================================================================
217
+ // Scenario 3: Decision & Document Workflow
218
+ // ===========================================================================
219
+ describe('Scenario 3: Decision & Document Workflow', () => {
220
+ it('should create decisions and track history', async () => {
221
+ const deps = runtime.getEntityManagementDeps();
222
+ const decisionDeps = runtime.getDecisionDocumentDeps();
223
+ // Create a decision using createEntity
224
+ const decision = await createEntity({
225
+ type: 'decision',
226
+ data: {
227
+ title: 'Use PostgreSQL for persistence',
228
+ context: 'We need a reliable database for production',
229
+ decision: 'Adopt PostgreSQL as our primary database',
230
+ rationale: 'PostgreSQL offers ACID compliance and excellent performance',
231
+ workstream: 'infrastructure',
232
+ decided_by: 'Engineering Team',
233
+ },
234
+ }, deps);
235
+ expect(decision.id).toMatch(/^DEC-\d{3}$/);
236
+ expect(decision.entity.title).toBe('Use PostgreSQL for persistence');
237
+ // Decisions are created with 'Pending' status (initial status)
238
+ expect(decision.entity.status).toBe('Pending');
239
+ // Get decision history - takes topic/workstream, not id
240
+ const history = await getDecisionHistory({
241
+ topic: 'PostgreSQL',
242
+ workstream: 'infrastructure',
243
+ }, decisionDeps);
244
+ expect(history.decisions.length).toBeGreaterThanOrEqual(1);
245
+ expect(history.decisions[0].id).toBe(decision.id);
246
+ });
247
+ it('should create decision that enables entities', async () => {
248
+ const deps = runtime.getEntityManagementDeps();
249
+ // Create a story first
250
+ const story = await createEntity({
251
+ type: 'story',
252
+ data: {
253
+ title: 'Implement caching',
254
+ workstream: 'engineering',
255
+ outcome: 'Improve performance with caching',
256
+ },
257
+ }, deps);
258
+ // Create decision that blocks the story using createEntity
259
+ const decision = await createEntity({
260
+ type: 'decision',
261
+ data: {
262
+ title: 'Use Redis for caching',
263
+ context: 'Need fast caching solution',
264
+ decision: 'Adopt Redis',
265
+ rationale: 'Redis is fast and well-supported',
266
+ workstream: 'engineering',
267
+ decided_by: 'Engineering Team',
268
+ blocks: [story.id],
269
+ },
270
+ }, deps);
271
+ expect(decision.entity.title).toBe('Use Redis for caching');
272
+ });
273
+ });
274
+ // ===========================================================================
275
+ // Scenario 4: Batch Operations
276
+ // ===========================================================================
277
+ describe('Scenario 4: Batch Operations', () => {
278
+ it('should perform batch create operations', async () => {
279
+ const batchDeps = runtime.getBatchOperationsDeps();
280
+ // batchUpdate with create operations
281
+ const result = await batchUpdate({
282
+ ops: [
283
+ {
284
+ op: 'create',
285
+ client_id: 'batch-m1',
286
+ type: 'milestone',
287
+ payload: { title: 'Batch Milestone 1', workstream: 'engineering' },
288
+ },
289
+ {
290
+ op: 'create',
291
+ client_id: 'batch-m2',
292
+ type: 'milestone',
293
+ payload: { title: 'Batch Milestone 2', workstream: 'engineering' },
294
+ },
295
+ {
296
+ op: 'create',
297
+ client_id: 'batch-s1',
298
+ type: 'story',
299
+ payload: { title: 'Batch Story 1', workstream: 'engineering', outcome: 'Test outcome' },
300
+ },
301
+ ],
302
+ }, batchDeps);
303
+ expect(result.results.length).toBe(3);
304
+ expect(result.summary.succeeded).toBe(3);
305
+ });
306
+ it('should batch update status', async () => {
307
+ const deps = runtime.getEntityManagementDeps();
308
+ const batchDeps = runtime.getBatchOperationsDeps();
309
+ // Create entities
310
+ const m1 = await createEntity({
311
+ type: 'milestone',
312
+ data: { title: 'M1', workstream: 'eng' },
313
+ }, deps);
314
+ const m2 = await createEntity({
315
+ type: 'milestone',
316
+ data: { title: 'M2', workstream: 'eng' },
317
+ }, deps);
318
+ // Batch update status using batchUpdate
319
+ const result = await batchUpdate({
320
+ ops: [
321
+ { op: 'update', client_id: 'upd-m1', id: m1.id, payload: { status: 'In Progress' } },
322
+ { op: 'update', client_id: 'upd-m2', id: m2.id, payload: { status: 'In Progress' } },
323
+ ],
324
+ }, batchDeps);
325
+ expect(result.results.length).toBe(2);
326
+ expect(result.summary.succeeded).toBe(2);
327
+ });
328
+ });
329
+ // ===========================================================================
330
+ // Scenario 5: Project Understanding
331
+ // ===========================================================================
332
+ describe('Scenario 5: Project Understanding', () => {
333
+ it('should get project overview', async () => {
334
+ const deps = runtime.getEntityManagementDeps();
335
+ const projectDeps = runtime.getProjectUnderstandingDeps();
336
+ // Create some entities
337
+ await createEntity({
338
+ type: 'milestone',
339
+ data: { title: 'Q1 Goals', workstream: 'product' },
340
+ }, deps);
341
+ await createEntity({
342
+ type: 'story',
343
+ data: { title: 'Feature A', workstream: 'product', outcome: 'Test outcome' },
344
+ }, deps);
345
+ await runtime.initialize();
346
+ const overview = await getProjectOverview({}, projectDeps);
347
+ // GetProjectOverviewOutput has summary.milestones, summary.stories, etc.
348
+ expect(overview.summary.milestones.total).toBeGreaterThanOrEqual(1);
349
+ expect(overview.summary.stories.total).toBeGreaterThanOrEqual(1);
350
+ });
351
+ it('should get workstream status', async () => {
352
+ const deps = runtime.getEntityManagementDeps();
353
+ const projectDeps = runtime.getProjectUnderstandingDeps();
354
+ await createEntity({
355
+ type: 'milestone',
356
+ data: { title: 'Security Audit', workstream: 'security' },
357
+ }, deps);
358
+ await createEntity({
359
+ type: 'task',
360
+ data: { title: 'Pen Testing', workstream: 'security', goal: 'Run penetration tests' },
361
+ }, deps);
362
+ await runtime.initialize();
363
+ const status = await getWorkstreamStatus({
364
+ workstream: 'security',
365
+ }, projectDeps);
366
+ expect(status.workstream).toBe('security');
367
+ // GetWorkstreamStatusOutput has summary.total
368
+ expect(status.summary.total).toBeGreaterThanOrEqual(2);
369
+ });
370
+ it('should analyze project state', async () => {
371
+ const deps = runtime.getEntityManagementDeps();
372
+ const projectDeps = runtime.getProjectUnderstandingDeps();
373
+ // Create milestone with story
374
+ const milestone = await createEntity({
375
+ type: 'milestone',
376
+ data: { title: 'Release 1.0', workstream: 'engineering' },
377
+ }, deps);
378
+ await createEntity({
379
+ type: 'story',
380
+ data: { title: 'Core Feature', workstream: 'engineering', parent: milestone.id, outcome: 'Test' },
381
+ }, deps);
382
+ await runtime.initialize();
383
+ const analysis = await analyzeProjectState({}, projectDeps);
384
+ // AnalyzeProjectStateOutput has health.overall
385
+ expect(analysis.health.overall).toBeDefined();
386
+ });
387
+ });
388
+ // ===========================================================================
389
+ // Scenario 6: Archive & Restore Workflow
390
+ // ===========================================================================
391
+ describe('Scenario 6: Archive & Restore Workflow', () => {
392
+ it('should archive an entity', async () => {
393
+ const deps = runtime.getEntityManagementDeps();
394
+ // Create entity
395
+ const milestone = await createEntity({
396
+ type: 'milestone',
397
+ data: { title: 'Old Project', workstream: 'legacy' },
398
+ }, deps);
399
+ // Update to In Progress first, then Completed (valid transition)
400
+ await updateEntityStatus({
401
+ id: milestone.id,
402
+ status: 'In Progress',
403
+ }, deps);
404
+ await updateEntityStatus({
405
+ id: milestone.id,
406
+ status: 'Completed',
407
+ }, deps);
408
+ // Archive
409
+ const archiveResult = await archiveEntity({
410
+ id: milestone.id,
411
+ force: true,
412
+ }, deps);
413
+ expect(archiveResult.archived).toBe(true);
414
+ expect(archiveResult.archive_path).toContain('archive');
415
+ // Verify entity is archived
416
+ const entity = await deps.getEntity(milestone.id);
417
+ expect(entity?.archived).toBe(true);
418
+ });
419
+ });
420
+ // ===========================================================================
421
+ // Scenario 8: Dependencies & Relationships
422
+ // ===========================================================================
423
+ describe('Scenario 8: Dependencies & Relationships', () => {
424
+ it('should create entities with dependencies', async () => {
425
+ const deps = runtime.getEntityManagementDeps();
426
+ const searchDeps = runtime.getSearchNavigationDeps();
427
+ // Create decision
428
+ await createEntity({
429
+ type: 'decision',
430
+ data: {
431
+ title: 'Use TypeScript',
432
+ workstream: 'engineering',
433
+ context: 'Need type safety',
434
+ decision: 'Adopt TypeScript',
435
+ rationale: 'Better developer experience',
436
+ },
437
+ }, deps);
438
+ // Create story
439
+ const story = await createEntity({
440
+ type: 'story',
441
+ data: {
442
+ title: 'Refactor to TypeScript',
443
+ workstream: 'engineering',
444
+ outcome: 'Codebase uses TypeScript',
445
+ },
446
+ }, deps);
447
+ // Verify entities were created
448
+ const full = await getEntity({
449
+ id: story.id,
450
+ fields: ['id', 'title', 'type', 'status', 'workstream'],
451
+ }, searchDeps);
452
+ expect(full.id).toBe(story.id);
453
+ expect(full.title).toBe('Refactor to TypeScript');
454
+ });
455
+ it('should handle implements relationship', async () => {
456
+ const deps = runtime.getEntityManagementDeps();
457
+ // Create document
458
+ const doc = await createEntity({
459
+ type: 'document',
460
+ data: {
461
+ title: 'API Specification',
462
+ workstream: 'engineering',
463
+ doc_type: 'spec',
464
+ content: 'API design document',
465
+ },
466
+ }, deps);
467
+ // Create story that implements the document
468
+ const story = await createEntity({
469
+ type: 'story',
470
+ data: {
471
+ title: 'Implement API',
472
+ workstream: 'engineering',
473
+ implements: [doc.id],
474
+ outcome: 'API matches specification',
475
+ },
476
+ }, deps);
477
+ expect(story.entity.title).toBe('Implement API');
478
+ });
479
+ });
480
+ // ===========================================================================
481
+ // Scenario 9: Error Handling
482
+ // ===========================================================================
483
+ describe('Scenario 9: Error Handling', () => {
484
+ it('should handle invalid entity ID', async () => {
485
+ const searchDeps = runtime.getSearchNavigationDeps();
486
+ await expect(getEntity({
487
+ id: 'INVALID-999',
488
+ }, searchDeps)).rejects.toThrow();
489
+ });
490
+ it('should handle invalid status transition', async () => {
491
+ const deps = runtime.getEntityManagementDeps();
492
+ const milestone = await createEntity({
493
+ type: 'milestone',
494
+ data: { title: 'Test', workstream: 'test' },
495
+ }, deps);
496
+ // Try to go directly to Completed (should fail - need In Progress first)
497
+ await expect(updateEntityStatus({
498
+ id: milestone.id,
499
+ status: 'Completed',
500
+ }, deps)).rejects.toThrow();
501
+ });
502
+ it('should handle missing required fields', async () => {
503
+ const deps = runtime.getEntityManagementDeps();
504
+ await expect(createEntity({
505
+ type: 'milestone',
506
+ data: {
507
+ // Missing title
508
+ workstream: 'test',
509
+ },
510
+ }, deps)).rejects.toThrow();
511
+ });
512
+ });
513
+ // ===========================================================================
514
+ // Scenario 10: Status Cascade
515
+ // ===========================================================================
516
+ describe('Scenario 10: Status Cascade', () => {
517
+ it('should cascade status updates to children', async () => {
518
+ const deps = runtime.getEntityManagementDeps();
519
+ // Create hierarchy
520
+ const milestone = await createEntity({
521
+ type: 'milestone',
522
+ data: { title: 'Parent Milestone', workstream: 'eng' },
523
+ }, deps);
524
+ const story = await createEntity({
525
+ type: 'story',
526
+ data: { title: 'Child Story', workstream: 'eng', parent: milestone.id },
527
+ }, deps);
528
+ const task = await createEntity({
529
+ type: 'task',
530
+ data: { title: 'Grandchild Task', workstream: 'eng', parent: story.id, goal: 'Do work' },
531
+ }, deps);
532
+ // Update milestone to In Progress
533
+ await updateEntityStatus({
534
+ id: milestone.id,
535
+ status: 'In Progress',
536
+ }, deps);
537
+ // Update story to In Progress
538
+ await updateEntityStatus({
539
+ id: story.id,
540
+ status: 'In Progress',
541
+ }, deps);
542
+ // Update task to Completed
543
+ await updateEntityStatus({
544
+ id: task.id,
545
+ status: 'In Progress',
546
+ }, deps);
547
+ await updateEntityStatus({
548
+ id: task.id,
549
+ status: 'Completed',
550
+ }, deps);
551
+ // Verify task is completed
552
+ const taskEntity = await deps.getEntity(task.id);
553
+ expect(taskEntity?.status).toBe('Completed');
554
+ });
555
+ });
556
+ // ===========================================================================
557
+ // Scenario 11: Extended Archive Operations
558
+ // ===========================================================================
559
+ describe('Scenario 11: Extended Archive Operations', () => {
560
+ it('should archive milestone with children', async () => {
561
+ const deps = runtime.getEntityManagementDeps();
562
+ // Create hierarchy
563
+ const milestone = await createEntity({
564
+ type: 'milestone',
565
+ data: { title: 'Archive Test Milestone', workstream: 'archive-test' },
566
+ }, deps);
567
+ const story = await createEntity({
568
+ type: 'story',
569
+ data: { title: 'Archive Test Story', workstream: 'archive-test', parent: milestone.id },
570
+ }, deps);
571
+ const task = await createEntity({
572
+ type: 'task',
573
+ data: { title: 'Archive Test Task', workstream: 'archive-test', parent: story.id, goal: 'Test' },
574
+ }, deps);
575
+ // Complete the hierarchy (required for archiving)
576
+ await updateEntityStatus({ id: milestone.id, status: 'In Progress' }, deps);
577
+ await updateEntityStatus({ id: story.id, status: 'In Progress' }, deps);
578
+ await updateEntityStatus({ id: task.id, status: 'In Progress' }, deps);
579
+ await updateEntityStatus({ id: task.id, status: 'Completed' }, deps);
580
+ await updateEntityStatus({ id: story.id, status: 'Completed' }, deps);
581
+ await updateEntityStatus({ id: milestone.id, status: 'Completed' }, deps);
582
+ // Archive the milestone
583
+ const archiveResult = await archiveEntity({
584
+ id: milestone.id,
585
+ force: true,
586
+ }, deps);
587
+ expect(archiveResult.archived).toBe(true);
588
+ });
589
+ it('should restore archived entity', async () => {
590
+ const deps = runtime.getEntityManagementDeps();
591
+ // Create and complete entity
592
+ const milestone = await createEntity({
593
+ type: 'milestone',
594
+ data: { title: 'Restore Test', workstream: 'restore-test' },
595
+ }, deps);
596
+ await updateEntityStatus({ id: milestone.id, status: 'In Progress' }, deps);
597
+ await updateEntityStatus({ id: milestone.id, status: 'Completed' }, deps);
598
+ // Archive
599
+ await archiveEntity({ id: milestone.id, force: true }, deps);
600
+ // Restore
601
+ const restoreResult = await restoreFromArchive({
602
+ id: milestone.id,
603
+ restore_children: false,
604
+ }, deps);
605
+ expect(restoreResult.restored).toBe(true);
606
+ // Verify entity is no longer archived
607
+ const entity = await deps.getEntity(milestone.id);
608
+ expect(entity?.archived).toBe(false);
609
+ });
610
+ });
611
+ // ===========================================================================
612
+ // Scenario 12: Extended Decision & Document Operations
613
+ // ===========================================================================
614
+ describe('Scenario 12: Extended Decision & Document Operations', () => {
615
+ it('should create decision with blocks relationship', async () => {
616
+ const deps = runtime.getEntityManagementDeps();
617
+ // Create a story first
618
+ const story = await createEntity({
619
+ type: 'story',
620
+ data: { title: 'Story to Enable', workstream: 'decisions' },
621
+ }, deps);
622
+ // Create decision that blocks the story using createEntity
623
+ const decision = await createEntity({
624
+ type: 'decision',
625
+ data: {
626
+ title: 'Enable Story Decision',
627
+ context: 'We need to decide on the approach',
628
+ decision: 'Use approach A',
629
+ rationale: 'It is simpler',
630
+ workstream: 'decisions',
631
+ decided_by: 'team',
632
+ blocks: [story.id],
633
+ },
634
+ }, deps);
635
+ expect(decision.id).toMatch(/^DEC-\d{3}$/);
636
+ });
637
+ it('should get decision history by workstream', async () => {
638
+ const deps = runtime.getEntityManagementDeps();
639
+ const decisionDeps = runtime.getDecisionDocumentDeps();
640
+ // Create multiple decisions using createEntity
641
+ await createEntity({
642
+ type: 'decision',
643
+ data: {
644
+ title: 'Decision 1',
645
+ context: 'Context 1',
646
+ decision: 'Decision 1',
647
+ rationale: 'Rationale 1',
648
+ workstream: 'history-test',
649
+ decided_by: 'team',
650
+ },
651
+ }, deps);
652
+ await createEntity({
653
+ type: 'decision',
654
+ data: {
655
+ title: 'Decision 2',
656
+ context: 'Context 2',
657
+ decision: 'Decision 2',
658
+ rationale: 'Rationale 2',
659
+ workstream: 'history-test',
660
+ decided_by: 'team',
661
+ },
662
+ }, deps);
663
+ // Get history
664
+ const history = await getDecisionHistory({
665
+ workstream: 'history-test',
666
+ }, decisionDeps);
667
+ expect(history.decisions.length).toBeGreaterThanOrEqual(2);
668
+ });
669
+ });
670
+ // ===========================================================================
671
+ // Scenario 13: Extended Batch Operations
672
+ // ===========================================================================
673
+ describe('Scenario 13: Extended Batch Operations', () => {
674
+ it('should create entities with dependencies in batch', async () => {
675
+ const batchDeps = runtime.getBatchOperationsDeps();
676
+ const result = await batchUpdate({
677
+ ops: [
678
+ {
679
+ op: 'create',
680
+ client_id: 'batch-m',
681
+ type: 'milestone',
682
+ payload: { title: 'Batch Milestone', workstream: 'batch-deps' },
683
+ },
684
+ {
685
+ op: 'create',
686
+ client_id: 'batch-s',
687
+ type: 'story',
688
+ payload: { title: 'Batch Story', workstream: 'batch-deps', depends_on: ['@batch-m'] },
689
+ },
690
+ ],
691
+ }, batchDeps);
692
+ expect(result.results.length).toBe(2);
693
+ expect(result.summary.succeeded).toBe(2);
694
+ });
695
+ it('should batch update multiple entity statuses', async () => {
696
+ const deps = runtime.getEntityManagementDeps();
697
+ const batchDeps = runtime.getBatchOperationsDeps();
698
+ // Create entities
699
+ const m1 = await createEntity({
700
+ type: 'milestone',
701
+ data: { title: 'Batch Status M1', workstream: 'batch-status' },
702
+ }, deps);
703
+ const m2 = await createEntity({
704
+ type: 'milestone',
705
+ data: { title: 'Batch Status M2', workstream: 'batch-status' },
706
+ }, deps);
707
+ // Batch update to In Progress using batchUpdate
708
+ const result = await batchUpdate({
709
+ ops: [
710
+ { op: 'update', client_id: 'upd-m1', id: m1.id, payload: { status: 'In Progress' } },
711
+ { op: 'update', client_id: 'upd-m2', id: m2.id, payload: { status: 'In Progress' } },
712
+ ],
713
+ }, batchDeps);
714
+ expect(result.results.length).toBe(2);
715
+ expect(result.summary.succeeded).toBe(2);
716
+ // Verify statuses
717
+ const entity1 = await deps.getEntity(m1.id);
718
+ const entity2 = await deps.getEntity(m2.id);
719
+ expect(entity1?.status).toBe('In Progress');
720
+ expect(entity2?.status).toBe('In Progress');
721
+ });
722
+ });
723
+ // ===========================================================================
724
+ // Scenario 14: Navigate Hierarchy
725
+ // ===========================================================================
726
+ describe('Scenario 14: Navigate Hierarchy', () => {
727
+ it('should navigate up the hierarchy', async () => {
728
+ const deps = runtime.getEntityManagementDeps();
729
+ const searchDeps = runtime.getSearchNavigationDeps();
730
+ // Create hierarchy
731
+ const milestone = await createEntity({
732
+ type: 'milestone',
733
+ data: { title: 'Nav Milestone', workstream: 'nav' },
734
+ }, deps);
735
+ const story = await createEntity({
736
+ type: 'story',
737
+ data: { title: 'Nav Story', workstream: 'nav', parent: milestone.id },
738
+ }, deps);
739
+ const task = await createEntity({
740
+ type: 'task',
741
+ data: { title: 'Nav Task', workstream: 'nav', parent: story.id, goal: 'Navigate' },
742
+ }, deps);
743
+ // Navigate up from task using searchEntities
744
+ const upResult = await searchEntities({
745
+ from_id: task.id,
746
+ direction: 'up',
747
+ depth: 2,
748
+ }, searchDeps);
749
+ expect(upResult.results.length).toBeGreaterThanOrEqual(1);
750
+ });
751
+ it('should navigate down the hierarchy', async () => {
752
+ const deps = runtime.getEntityManagementDeps();
753
+ const searchDeps = runtime.getSearchNavigationDeps();
754
+ // Create hierarchy
755
+ const milestone = await createEntity({
756
+ type: 'milestone',
757
+ data: { title: 'Nav Down Milestone', workstream: 'nav-down' },
758
+ }, deps);
759
+ await createEntity({
760
+ type: 'story',
761
+ data: { title: 'Nav Down Story 1', workstream: 'nav-down', parent: milestone.id },
762
+ }, deps);
763
+ await createEntity({
764
+ type: 'story',
765
+ data: { title: 'Nav Down Story 2', workstream: 'nav-down', parent: milestone.id },
766
+ }, deps);
767
+ // Navigate down from milestone using searchEntities
768
+ const downResult = await searchEntities({
769
+ from_id: milestone.id,
770
+ direction: 'down',
771
+ depth: 1,
772
+ }, searchDeps);
773
+ expect(downResult.results.length).toBeGreaterThanOrEqual(2);
774
+ });
775
+ it('should get siblings', async () => {
776
+ const deps = runtime.getEntityManagementDeps();
777
+ const searchDeps = runtime.getSearchNavigationDeps();
778
+ // Create parent and siblings
779
+ const milestone = await createEntity({
780
+ type: 'milestone',
781
+ data: { title: 'Sibling Parent', workstream: 'siblings' },
782
+ }, deps);
783
+ const story1 = await createEntity({
784
+ type: 'story',
785
+ data: { title: 'Sibling 1', workstream: 'siblings', parent: milestone.id },
786
+ }, deps);
787
+ await createEntity({
788
+ type: 'story',
789
+ data: { title: 'Sibling 2', workstream: 'siblings', parent: milestone.id },
790
+ }, deps);
791
+ // Get siblings of story1 using searchEntities
792
+ const siblingsResult = await searchEntities({
793
+ from_id: story1.id,
794
+ direction: 'siblings',
795
+ }, searchDeps);
796
+ expect(siblingsResult.results.length).toBeGreaterThanOrEqual(1);
797
+ });
798
+ });
799
+ // ===========================================================================
800
+ // Scenario 15: Efficiency Improvements
801
+ // ===========================================================================
802
+ describe('Scenario 15: Efficiency Improvements', () => {
803
+ it('should return entities with batch_update when include_entities=true', async () => {
804
+ const deps = runtime.getEntityManagementDeps();
805
+ const batchDeps = runtime.getBatchOperationsDeps();
806
+ // Create a story first
807
+ const story = await createEntity({
808
+ type: 'story',
809
+ data: { title: 'Batch Test Story', workstream: 'batch-test' },
810
+ }, deps);
811
+ // Update with include_entities=true
812
+ const result = await batchUpdate({
813
+ ops: [
814
+ { client_id: 'u1', op: 'update', id: story.id, payload: { status: 'In Progress' } },
815
+ ],
816
+ options: { include_entities: true },
817
+ }, batchDeps);
818
+ expect(result.summary.succeeded).toBe(1);
819
+ expect(result.results[0].entity).toBeDefined();
820
+ expect(result.results[0].entity?.id).toBe(story.id);
821
+ expect(result.results[0].entity?.title).toBe('Batch Test Story');
822
+ });
823
+ it('should NOT return entities with batch_update when include_entities=false', async () => {
824
+ const deps = runtime.getEntityManagementDeps();
825
+ const batchDeps = runtime.getBatchOperationsDeps();
826
+ // Create a story first
827
+ const story = await createEntity({
828
+ type: 'story',
829
+ data: { title: 'Batch Test Story 2', workstream: 'batch-test' },
830
+ }, deps);
831
+ // Update with include_entities=false (default)
832
+ const result = await batchUpdate({
833
+ ops: [
834
+ { client_id: 'u1', op: 'update', id: story.id, payload: { status: 'In Progress' } },
835
+ ],
836
+ options: { include_entities: false },
837
+ }, batchDeps);
838
+ expect(result.summary.succeeded).toBe(1);
839
+ expect(result.results[0].entity).toBeUndefined();
840
+ });
841
+ it('should filter fields with batch_update when fields option is provided', async () => {
842
+ const deps = runtime.getEntityManagementDeps();
843
+ const batchDeps = runtime.getBatchOperationsDeps();
844
+ // Create a story first
845
+ const story = await createEntity({
846
+ type: 'story',
847
+ data: { title: 'Batch Field Test', workstream: 'batch-test' },
848
+ }, deps);
849
+ // Update with include_entities=true and specific fields
850
+ const result = await batchUpdate({
851
+ ops: [
852
+ { client_id: 'u1', op: 'update', id: story.id, payload: { status: 'In Progress' } },
853
+ ],
854
+ options: { include_entities: true, fields: ['id', 'title', 'status'] },
855
+ }, batchDeps);
856
+ expect(result.summary.succeeded).toBe(1);
857
+ expect(result.results[0].entity).toBeDefined();
858
+ expect(result.results[0].entity?.id).toBe(story.id);
859
+ expect(result.results[0].entity?.title).toBe('Batch Field Test');
860
+ // Should not have content since we only requested id, title, status
861
+ expect(result.results[0].entity?.content).toBeUndefined();
862
+ });
863
+ it('should preview changes with batch_update dry_run=true', async () => {
864
+ const deps = runtime.getEntityManagementDeps();
865
+ const batchDeps = runtime.getBatchOperationsDeps();
866
+ // Create a story first
867
+ const story = await createEntity({
868
+ type: 'story',
869
+ data: { title: 'Dry Run Test', workstream: 'dry-run-test', priority: 'Medium' },
870
+ }, deps);
871
+ // Update with dry_run=true - test multiple fields including non-standard ones
872
+ const result = await batchUpdate({
873
+ ops: [
874
+ { client_id: 'u1', op: 'update', id: story.id, payload: { title: 'Changed Title', priority: 'High' } },
875
+ ],
876
+ options: { dry_run: true },
877
+ }, batchDeps);
878
+ // Should have dry_run flag and would_update array
879
+ expect(result.dry_run).toBe(true);
880
+ expect(result.would_update).toBeDefined();
881
+ expect(result.would_update?.length).toBe(1);
882
+ expect(result.would_update?.[0].client_id).toBe('u1');
883
+ expect(result.would_update?.[0].id).toBe(story.id);
884
+ expect(result.would_update?.[0].op).toBe('update');
885
+ expect(result.would_update?.[0].changes.length).toBeGreaterThan(0);
886
+ // Verify the changes array contains the actual field changes
887
+ const titleChange = result.would_update?.[0].changes.find(c => c.field === 'title');
888
+ expect(titleChange).toBeDefined();
889
+ expect(titleChange?.before).toBe('Dry Run Test');
890
+ expect(titleChange?.after).toBe('Changed Title');
891
+ const priorityChange = result.would_update?.[0].changes.find(c => c.field === 'priority');
892
+ expect(priorityChange).toBeDefined();
893
+ expect(priorityChange?.before).toBe('Medium');
894
+ expect(priorityChange?.after).toBe('High');
895
+ // Verify entity was NOT actually updated - title should still be 'Dry Run Test'
896
+ const entityAfter = await deps.getEntity(story.id);
897
+ expect(entityAfter?.title).toBe('Dry Run Test');
898
+ });
899
+ it('should return detailed changes with reconcile_relationships', async () => {
900
+ // Test reconcile_relationships returns enhanced output
901
+ const result = await runtime.reconcileImplementsRelationships({ dry_run: true });
902
+ // Should have the new output format
903
+ expect(result).toHaveProperty('scanned');
904
+ expect(result).toHaveProperty('updated');
905
+ expect(result).toHaveProperty('dry_run');
906
+ expect(result).toHaveProperty('changes');
907
+ expect(result).toHaveProperty('warnings');
908
+ expect(result).toHaveProperty('details'); // Legacy format
909
+ // dry_run should be true
910
+ expect(result.dry_run).toBe(true);
911
+ // updated should be 0 in dry_run mode
912
+ expect(result.updated).toBe(0);
913
+ });
914
+ it('should return changes array with update_entity', async () => {
915
+ const deps = runtime.getEntityManagementDeps();
916
+ // Create a story
917
+ const story = await createEntity({
918
+ type: 'story',
919
+ data: {
920
+ title: 'Changes Test Story',
921
+ workstream: 'core',
922
+ status: 'Not Started',
923
+ priority: 'Medium',
924
+ },
925
+ }, deps);
926
+ // Update the story
927
+ const result = await updateEntity({
928
+ id: story.id,
929
+ data: { title: 'Updated Title', priority: 'High' },
930
+ }, deps);
931
+ // Should have changes array
932
+ expect(result.changes).toBeDefined();
933
+ expect(result.changes.length).toBeGreaterThan(0);
934
+ // Find title change
935
+ const titleChange = result.changes.find(c => c.field === 'title');
936
+ expect(titleChange).toBeDefined();
937
+ expect(titleChange.before).toBe('Changes Test Story');
938
+ expect(titleChange.after).toBe('Updated Title');
939
+ // Find priority change
940
+ const priorityChange = result.changes.find(c => c.field === 'priority');
941
+ expect(priorityChange).toBeDefined();
942
+ expect(priorityChange.before).toBe('Medium');
943
+ expect(priorityChange.after).toBe('High');
944
+ });
945
+ it('should not duplicate dataview blocks on feature update', async () => {
946
+ const deps = runtime.getEntityManagementDeps();
947
+ // Create a feature
948
+ const feature = await createEntity({
949
+ type: 'feature',
950
+ data: {
951
+ title: 'Dataview Test Feature',
952
+ workstream: 'core',
953
+ status: 'Planned',
954
+ tier: 'OSS',
955
+ phase: 'MVP',
956
+ user_story: 'As a user, I want to test dataview blocks',
957
+ },
958
+ }, deps);
959
+ // Update the feature multiple times
960
+ await updateEntity({
961
+ id: feature.id,
962
+ data: { tier: 'Premium' },
963
+ }, deps);
964
+ await updateEntity({
965
+ id: feature.id,
966
+ data: { phase: 'GA' },
967
+ }, deps);
968
+ // Get the entity content
969
+ const entity = await deps.getEntity(feature.id);
970
+ expect(entity).toBeDefined();
971
+ // Check that dataview blocks are not duplicated
972
+ // The content should have at most one instance of each dataview section
973
+ const content = entity.content || '';
974
+ const implementedByMatches = content.match(/## 🔗 Implemented By/g);
975
+ expect(implementedByMatches?.length || 0).toBeLessThanOrEqual(1);
976
+ });
977
+ });
978
+ });
979
+ //# sourceMappingURL=integration.test.js.map