obsidian-accomplishments-mcp 0.1.10 → 0.1.11

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 +154 -182
  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,851 @@
1
+ /**
2
+ * V2 Entity Management Tools
3
+ *
4
+ * MCP tools for creating, updating, archiving, and restoring entities.
5
+ */
6
+ import { generateCssClasses } from '../services/v2/entity-serializer.js';
7
+ import { workstreamNormalizer } from '../services/v2/workstream-normalizer.js';
8
+ // =============================================================================
9
+ // Relationship Validation Constants
10
+ // =============================================================================
11
+ /**
12
+ * Valid target types for Decision.affects field.
13
+ * Decisions can affect: documents, stories, tasks (NOT milestones)
14
+ */
15
+ const DECISION_AFFECTS_VALID_TYPES = ['document', 'story', 'task'];
16
+ /**
17
+ * Valid target types for Document.implemented_by field.
18
+ * Documents can be implemented by: stories, tasks (NOT milestones)
19
+ */
20
+ const DOCUMENT_IMPLEMENTED_BY_VALID_TYPES = ['story', 'task'];
21
+ /**
22
+ * Valid target types for depends_on fields by entity type.
23
+ */
24
+ const DEPENDS_ON_VALID_TYPES = {
25
+ milestone: ['milestone', 'decision'],
26
+ story: ['story', 'decision', 'document'],
27
+ task: ['task', 'decision'],
28
+ decision: ['decision'],
29
+ document: ['document', 'decision'],
30
+ feature: ['feature', 'decision'],
31
+ };
32
+ /**
33
+ * Validate entity relationships before creation/update.
34
+ * Checks that:
35
+ * 1. All referenced entity IDs exist (or are in the batch being created)
36
+ * 2. Relationship types are valid (e.g., decisions can't enable milestones)
37
+ *
38
+ * @param type - The entity type being created/updated
39
+ * @param data - The entity data containing relationships
40
+ * @param deps - Dependencies for entity lookup
41
+ * @param batchIds - Optional map of IDs being created in the same batch (id -> type)
42
+ */
43
+ export function validateRelationships(type, data, deps, batchIds) {
44
+ const errors = [];
45
+ // Helper to check if an ID exists (in cache or batch)
46
+ const idExists = (id) => {
47
+ return deps.entityExists(id) || (batchIds?.has(id) ?? false);
48
+ };
49
+ // Helper to get entity type (from cache or batch)
50
+ const getType = (id) => {
51
+ const cachedType = deps.getEntityType(id);
52
+ if (cachedType)
53
+ return cachedType;
54
+ return batchIds?.get(id) ?? null;
55
+ };
56
+ // Validate parent reference
57
+ const parent = data.parent;
58
+ if (parent) {
59
+ if (!idExists(parent)) {
60
+ errors.push({
61
+ field: 'parent',
62
+ message: `Parent entity '${parent}' does not exist`,
63
+ invalidId: parent,
64
+ });
65
+ }
66
+ else {
67
+ // Validate parent type
68
+ const parentType = getType(parent);
69
+ if (type === 'story' && parentType && parentType !== 'milestone') {
70
+ errors.push({
71
+ field: 'parent',
72
+ message: `Story parent must be a milestone, got '${parentType}'`,
73
+ invalidId: parent,
74
+ });
75
+ }
76
+ if (type === 'task' && parentType && parentType !== 'story') {
77
+ errors.push({
78
+ field: 'parent',
79
+ message: `Task parent must be a story, got '${parentType}'`,
80
+ invalidId: parent,
81
+ });
82
+ }
83
+ }
84
+ }
85
+ // Validate depends_on references
86
+ const dependsOn = data.depends_on;
87
+ if (dependsOn && dependsOn.length > 0) {
88
+ const validTypes = DEPENDS_ON_VALID_TYPES[type];
89
+ for (const depId of dependsOn) {
90
+ if (!idExists(depId)) {
91
+ errors.push({
92
+ field: 'depends_on',
93
+ message: `Dependency '${depId}' does not exist`,
94
+ invalidId: depId,
95
+ });
96
+ }
97
+ else {
98
+ const depType = getType(depId);
99
+ if (depType && !validTypes.includes(depType)) {
100
+ errors.push({
101
+ field: 'depends_on',
102
+ message: `${type} cannot depend on ${depType} '${depId}'. Valid types: ${validTypes.join(', ')}`,
103
+ invalidId: depId,
104
+ });
105
+ }
106
+ }
107
+ }
108
+ }
109
+ // Validate implements references (for stories and milestones)
110
+ const implementsField = data.implements;
111
+ if (implementsField && implementsField.length > 0) {
112
+ for (const implId of implementsField) {
113
+ if (!idExists(implId)) {
114
+ errors.push({
115
+ field: 'implements',
116
+ message: `Document '${implId}' does not exist`,
117
+ invalidId: implId,
118
+ });
119
+ }
120
+ else {
121
+ const implType = getType(implId);
122
+ if (implType && implType !== 'document') {
123
+ errors.push({
124
+ field: 'implements',
125
+ message: `Can only implement documents, got ${implType} '${implId}'`,
126
+ invalidId: implId,
127
+ });
128
+ }
129
+ }
130
+ }
131
+ }
132
+ // Validate affects references (for decisions)
133
+ if (type === 'decision') {
134
+ const affects = data.affects;
135
+ if (affects && affects.length > 0) {
136
+ for (const affectedId of affects) {
137
+ if (!idExists(affectedId)) {
138
+ errors.push({
139
+ field: 'affects',
140
+ message: `Entity '${affectedId}' does not exist`,
141
+ invalidId: affectedId,
142
+ });
143
+ }
144
+ else {
145
+ const affectedType = getType(affectedId);
146
+ if (affectedType && !DECISION_AFFECTS_VALID_TYPES.includes(affectedType)) {
147
+ errors.push({
148
+ field: 'affects',
149
+ message: `Decision cannot affect ${affectedType} '${affectedId}'. Valid types: ${DECISION_AFFECTS_VALID_TYPES.join(', ')}`,
150
+ invalidId: affectedId,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ // Validate implemented_by references (for documents)
158
+ if (type === 'document') {
159
+ const implementedBy = data.implemented_by;
160
+ if (implementedBy && implementedBy.length > 0) {
161
+ for (const implById of implementedBy) {
162
+ if (!idExists(implById)) {
163
+ errors.push({
164
+ field: 'implemented_by',
165
+ message: `Entity '${implById}' does not exist`,
166
+ invalidId: implById,
167
+ });
168
+ }
169
+ else {
170
+ const implByType = getType(implById);
171
+ if (implByType && !DOCUMENT_IMPLEMENTED_BY_VALID_TYPES.includes(implByType)) {
172
+ errors.push({
173
+ field: 'implemented_by',
174
+ message: `Document cannot be implemented by ${implByType} '${implById}'. Valid types: ${DOCUMENT_IMPLEMENTED_BY_VALID_TYPES.join(', ')}`,
175
+ invalidId: implById,
176
+ });
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ // Validate supersedes reference (for decisions)
183
+ if (type === 'decision') {
184
+ const supersedes = data.supersedes;
185
+ if (supersedes) {
186
+ if (!idExists(supersedes)) {
187
+ errors.push({
188
+ field: 'supersedes',
189
+ message: `Decision '${supersedes}' does not exist`,
190
+ invalidId: supersedes,
191
+ });
192
+ }
193
+ else {
194
+ const supersedesType = getType(supersedes);
195
+ if (supersedesType && supersedesType !== 'decision') {
196
+ errors.push({
197
+ field: 'supersedes',
198
+ message: `Can only supersede decisions, got ${supersedesType} '${supersedes}'`,
199
+ invalidId: supersedes,
200
+ });
201
+ }
202
+ }
203
+ }
204
+ }
205
+ return errors;
206
+ }
207
+ // =============================================================================
208
+ // Tool Implementations
209
+ // =============================================================================
210
+ /**
211
+ * Create a new entity with optional dependencies and relationships.
212
+ */
213
+ export async function createEntity(input, deps) {
214
+ const { type, data, options } = input;
215
+ // Validate relationships before creating
216
+ const validationErrors = validateRelationships(type, data, deps);
217
+ if (validationErrors.length > 0) {
218
+ const errorMessages = validationErrors.map(e => `${e.field}: ${e.message}`).join('; ');
219
+ throw new Error(`Invalid relationships: ${errorMessages}`);
220
+ }
221
+ // Normalize workstream
222
+ const workstreamResult = workstreamNormalizer.normalize(data.workstream);
223
+ const normalizedWorkstream = workstreamResult.normalized;
224
+ // Generate new ID
225
+ const id = await deps.getNextId(type);
226
+ // Build base entity
227
+ const now = deps.getCurrentTimestamp();
228
+ const baseEntity = {
229
+ id,
230
+ type,
231
+ title: data.title,
232
+ workstream: normalizedWorkstream,
233
+ created_at: now,
234
+ updated_at: now,
235
+ archived: false,
236
+ canvas_source: options?.canvas_source,
237
+ };
238
+ // Build type-specific entity
239
+ let entity;
240
+ switch (type) {
241
+ case 'milestone':
242
+ entity = buildMilestone(baseEntity, data);
243
+ break;
244
+ case 'story':
245
+ entity = buildStory(baseEntity, data);
246
+ break;
247
+ case 'task':
248
+ entity = buildTask(baseEntity, data);
249
+ break;
250
+ case 'decision':
251
+ entity = buildDecision(baseEntity, data);
252
+ break;
253
+ case 'document':
254
+ entity = buildDocument(baseEntity, data);
255
+ break;
256
+ case 'feature':
257
+ entity = buildFeature(baseEntity, data);
258
+ break;
259
+ default:
260
+ throw new Error(`Unknown entity type: ${type}`);
261
+ }
262
+ // Write entity to file
263
+ await deps.writeEntity(entity);
264
+ // Add to canvas if requested
265
+ let canvasNodeAdded = false;
266
+ if (options?.add_to_canvas !== false && deps.addToCanvas && options?.canvas_source) {
267
+ canvasNodeAdded = await deps.addToCanvas(entity, options.canvas_source);
268
+ }
269
+ // Convert to full representation
270
+ const entityFull = await deps.toEntityFull(entity);
271
+ // Build result with optional normalization message
272
+ const result = {
273
+ id,
274
+ entity: entityFull,
275
+ dependencies_created: data.depends_on?.length ?? 0,
276
+ canvas_node_added: canvasNodeAdded,
277
+ };
278
+ // Add normalization message if workstream was normalized
279
+ if (workstreamResult.wasNormalized && workstreamResult.message) {
280
+ result.messages = result.messages || [];
281
+ result.messages.push(workstreamResult.message);
282
+ }
283
+ return result;
284
+ }
285
+ // =============================================================================
286
+ // Entity Builders
287
+ // =============================================================================
288
+ function buildMilestone(base, data) {
289
+ const entity = {
290
+ ...base,
291
+ type: 'milestone',
292
+ status: data.status || 'Not Started',
293
+ priority: data.priority,
294
+ target_date: data.target_date,
295
+ owner: data.owner,
296
+ depends_on: data.depends_on || [],
297
+ };
298
+ // Auto-generate cssclasses if not provided
299
+ entity.cssclasses = data.cssclasses || generateCssClasses(entity);
300
+ return entity;
301
+ }
302
+ function buildStory(base, data) {
303
+ const entity = {
304
+ ...base,
305
+ type: 'story',
306
+ status: data.status || 'Not Started',
307
+ parent: data.parent,
308
+ priority: data.priority,
309
+ depends_on: data.depends_on || [],
310
+ implements: data.implements || [],
311
+ acceptance_criteria: data.acceptance_criteria || [],
312
+ tasks: data.tasks || [],
313
+ };
314
+ // Auto-generate cssclasses if not provided
315
+ entity.cssclasses = data.cssclasses || generateCssClasses(entity);
316
+ return entity;
317
+ }
318
+ function buildTask(base, data) {
319
+ const entity = {
320
+ ...base,
321
+ type: 'task',
322
+ status: data.status || 'Not Started',
323
+ parent: data.parent,
324
+ goal: data.goal || '',
325
+ estimate_hrs: data.estimate_hrs,
326
+ actual_hrs: data.actual_hrs,
327
+ assignee: data.assignee,
328
+ description: data.description,
329
+ technical_notes: data.technical_notes,
330
+ notes: data.notes,
331
+ };
332
+ // Auto-generate cssclasses if not provided
333
+ entity.cssclasses = data.cssclasses || generateCssClasses(entity);
334
+ return entity;
335
+ }
336
+ function buildDecision(base, data) {
337
+ const entity = {
338
+ ...base,
339
+ type: 'decision',
340
+ status: data.status || 'Pending',
341
+ decided_by: data.decided_by,
342
+ decided_on: data.decided_on,
343
+ supersedes: data.supersedes,
344
+ affects: data.affects || [],
345
+ depends_on: data.depends_on || [],
346
+ };
347
+ // Auto-generate cssclasses if not provided
348
+ entity.cssclasses = data.cssclasses || generateCssClasses(entity);
349
+ return entity;
350
+ }
351
+ function buildDocument(base, data) {
352
+ const entity = {
353
+ ...base,
354
+ type: 'document',
355
+ doc_type: data.doc_type || 'spec',
356
+ status: data.status || 'Draft',
357
+ version: data.version,
358
+ owner: data.owner,
359
+ implementation_context: data.implementation_context,
360
+ implemented_by: data.implemented_by || [],
361
+ previous_version: data.previous_version || [],
362
+ content: data.content,
363
+ };
364
+ // Auto-generate cssclasses if not provided
365
+ entity.cssclasses = data.cssclasses || generateCssClasses(entity);
366
+ return entity;
367
+ }
368
+ function buildFeature(base, data) {
369
+ const entity = {
370
+ ...base,
371
+ type: 'feature',
372
+ status: data.status || 'Planned',
373
+ user_story: data.user_story || '',
374
+ tier: data.tier || 'OSS',
375
+ phase: data.phase || 'MVP',
376
+ implemented_by: data.implemented_by || [],
377
+ documented_by: data.documented_by || [],
378
+ decided_by: data.decided_by || [],
379
+ test_refs: data.test_refs || [],
380
+ content: data.content,
381
+ };
382
+ // Auto-generate cssclasses if not provided
383
+ entity.cssclasses = data.cssclasses || generateCssClasses(entity);
384
+ return entity;
385
+ }
386
+ // =============================================================================
387
+ // Update Entity (Enhanced - consolidates status, archive, restore operations)
388
+ // =============================================================================
389
+ /**
390
+ * Update entity fields and/or modify relationships.
391
+ * Enhanced to support:
392
+ * - Status updates with validation and cascade (replaces update_entity_status)
393
+ * - Archive operations (replaces archive_entity, archive_milestone)
394
+ * - Restore operations (replaces restore_from_archive)
395
+ */
396
+ export async function updateEntity(input, deps) {
397
+ const { id, data, add_dependencies, remove_dependencies, add_to, remove_from, status, status_note, cascade, archived, archive_options, restore_options, } = input;
398
+ // Get existing entity
399
+ const entity = await deps.getEntity(id);
400
+ if (!entity) {
401
+ throw new Error(`Entity not found: ${id}`);
402
+ }
403
+ // Initialize result
404
+ const result = {
405
+ id,
406
+ entity: null, // Will be set at the end
407
+ dependencies_added: 0,
408
+ dependencies_removed: 0,
409
+ };
410
+ // Handle archive operation (takes precedence)
411
+ if (archived === true) {
412
+ const archiveResult = await handleArchiveOperation(entity, archive_options, deps);
413
+ result.archive_result = archiveResult;
414
+ // After archiving, get the updated entity
415
+ const archivedEntity = await deps.getEntity(id);
416
+ if (archivedEntity) {
417
+ result.entity = await deps.toEntityFull(archivedEntity);
418
+ }
419
+ return result;
420
+ }
421
+ // Handle restore operation
422
+ if (archived === false && entity.archived) {
423
+ const restoreResult = await handleRestoreOperation(entity, restore_options, deps);
424
+ result.restore_result = restoreResult;
425
+ // After restoring, get the updated entity
426
+ const restoredEntity = await deps.getEntity(id);
427
+ if (restoredEntity) {
428
+ result.entity = await deps.toEntityFull(restoredEntity);
429
+ }
430
+ return result;
431
+ }
432
+ // Apply field updates
433
+ const updatedEntity = { ...entity };
434
+ let workstreamNormalizationMessage;
435
+ if (data) {
436
+ // Normalize workstream if being updated
437
+ if (data.workstream !== undefined) {
438
+ const workstreamResult = workstreamNormalizer.normalize(data.workstream);
439
+ data.workstream = workstreamResult.normalized;
440
+ if (workstreamResult.wasNormalized && workstreamResult.message) {
441
+ workstreamNormalizationMessage = workstreamResult.message;
442
+ }
443
+ }
444
+ Object.assign(updatedEntity, data);
445
+ }
446
+ // Handle status update with validation and cascade
447
+ if (status && status !== entity.status) {
448
+ const statusResult = await handleStatusUpdate(entity, status, status_note, cascade, deps);
449
+ result.status_changed = statusResult;
450
+ updatedEntity.status = status;
451
+ }
452
+ // Handle dependency changes
453
+ if ('depends_on' in updatedEntity) {
454
+ const currentDeps = updatedEntity.depends_on || [];
455
+ if (add_dependencies) {
456
+ const newDeps = [...new Set([...currentDeps, ...add_dependencies])];
457
+ updatedEntity.depends_on = newDeps;
458
+ result.dependencies_added = newDeps.length - currentDeps.length;
459
+ }
460
+ if (remove_dependencies) {
461
+ const filtered = currentDeps.filter((d) => !remove_dependencies.includes(d));
462
+ updatedEntity.depends_on = filtered;
463
+ result.dependencies_removed = currentDeps.length - filtered.length;
464
+ }
465
+ }
466
+ // Handle relationship additions (implements, affects)
467
+ if (add_to) {
468
+ if (add_to.implements && 'implements' in updatedEntity) {
469
+ const current = updatedEntity.implements || [];
470
+ updatedEntity.implements = [...new Set([...current, ...add_to.implements])];
471
+ }
472
+ if (add_to.affects && 'affects' in updatedEntity) {
473
+ const current = updatedEntity.affects || [];
474
+ updatedEntity.affects = [...new Set([...current, ...add_to.affects])];
475
+ }
476
+ }
477
+ // Handle relationship removals
478
+ if (remove_from) {
479
+ if (remove_from.implements && 'implements' in updatedEntity) {
480
+ const current = updatedEntity.implements || [];
481
+ updatedEntity.implements = current.filter((i) => !remove_from.implements.includes(i));
482
+ }
483
+ if (remove_from.affects && 'affects' in updatedEntity) {
484
+ const current = updatedEntity.affects || [];
485
+ updatedEntity.affects = current.filter((e) => !remove_from.affects.includes(e));
486
+ }
487
+ }
488
+ // Update timestamp
489
+ updatedEntity.updated_at = deps.getCurrentTimestamp();
490
+ // Compute field changes (before/after diff)
491
+ const changes = computeFieldChanges(entity, updatedEntity);
492
+ if (changes.length > 0) {
493
+ result.changes = changes;
494
+ }
495
+ // Write updated entity
496
+ await deps.writeEntity(updatedEntity);
497
+ // Convert to full representation
498
+ result.entity = await deps.toEntityFull(updatedEntity);
499
+ // Add normalization message if workstream was normalized
500
+ if (workstreamNormalizationMessage) {
501
+ result.messages = result.messages || [];
502
+ result.messages.push(workstreamNormalizationMessage);
503
+ }
504
+ return result;
505
+ }
506
+ // =============================================================================
507
+ // Helper: Compute Field Changes (before/after diff)
508
+ // =============================================================================
509
+ /**
510
+ * Compare two entity states and return a list of field changes.
511
+ * For array fields, also computes added/removed items.
512
+ */
513
+ function computeFieldChanges(before, after) {
514
+ const changes = [];
515
+ // Get all keys from both objects
516
+ const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);
517
+ // Fields to skip (internal/computed)
518
+ const skipFields = new Set(['vault_path', 'created_at', 'updated_at']);
519
+ for (const key of allKeys) {
520
+ if (skipFields.has(key))
521
+ continue;
522
+ const beforeVal = before[key];
523
+ const afterVal = after[key];
524
+ // Check if values are different
525
+ const beforeStr = JSON.stringify(beforeVal);
526
+ const afterStr = JSON.stringify(afterVal);
527
+ if (beforeStr !== afterStr) {
528
+ const change = {
529
+ field: key,
530
+ before: beforeVal,
531
+ after: afterVal,
532
+ };
533
+ // For arrays, compute added/removed
534
+ if (Array.isArray(beforeVal) || Array.isArray(afterVal)) {
535
+ const beforeArr = Array.isArray(beforeVal) ? beforeVal : [];
536
+ const afterArr = Array.isArray(afterVal) ? afterVal : [];
537
+ const beforeSet = new Set(beforeArr.map(v => JSON.stringify(v)));
538
+ const afterSet = new Set(afterArr.map(v => JSON.stringify(v)));
539
+ const added = afterArr.filter(v => !beforeSet.has(JSON.stringify(v)));
540
+ const removed = beforeArr.filter(v => !afterSet.has(JSON.stringify(v)));
541
+ if (added.length > 0)
542
+ change.added = added;
543
+ if (removed.length > 0)
544
+ change.removed = removed;
545
+ }
546
+ changes.push(change);
547
+ }
548
+ }
549
+ return changes;
550
+ }
551
+ // =============================================================================
552
+ // Helper: Handle Status Update
553
+ // =============================================================================
554
+ async function handleStatusUpdate(entity, newStatus, note, cascade, deps) {
555
+ const oldStatus = entity.status;
556
+ // Validate transition
557
+ const validation = deps.validateStatusTransition(entity, newStatus);
558
+ if (!validation.valid) {
559
+ throw new Error(`Invalid status transition: ${validation.reason}`);
560
+ }
561
+ // Compute cascade effects if requested
562
+ let cascadedUpdates = [];
563
+ if (cascade) {
564
+ cascadedUpdates = await deps.computeCascadeEffects(entity, newStatus);
565
+ }
566
+ return {
567
+ old_status: oldStatus,
568
+ new_status: newStatus,
569
+ cascaded_updates: cascadedUpdates,
570
+ };
571
+ }
572
+ // =============================================================================
573
+ // Helper: Handle Archive Operation
574
+ // =============================================================================
575
+ async function handleArchiveOperation(entity, options, deps) {
576
+ const { force, cascade: archiveCascade, archive_folder, remove_from_canvas, canvas_source } = options || {};
577
+ // For milestones with cascade, archive all children
578
+ if (entity.type === 'milestone' && archiveCascade) {
579
+ const archivedChildren = [];
580
+ // Compute archive path
581
+ const now = new Date();
582
+ const quarter = Math.ceil((now.getMonth() + 1) / 3);
583
+ const archivePath = archive_folder || `archive/${now.getFullYear()}-Q${quarter}`;
584
+ // Get all children (stories)
585
+ const stories = await deps.getChildren(entity.id);
586
+ for (const story of stories) {
587
+ // Get tasks for each story
588
+ const tasks = await deps.getChildren(story.id);
589
+ for (const task of tasks) {
590
+ await deps.moveToArchive(task.id, archivePath);
591
+ archivedChildren.push(task.id);
592
+ if (remove_from_canvas && deps.removeFromCanvas && canvas_source) {
593
+ await deps.removeFromCanvas(task.id, canvas_source);
594
+ }
595
+ }
596
+ await deps.moveToArchive(story.id, archivePath);
597
+ archivedChildren.push(story.id);
598
+ if (remove_from_canvas && deps.removeFromCanvas && canvas_source) {
599
+ await deps.removeFromCanvas(story.id, canvas_source);
600
+ }
601
+ }
602
+ // Archive the milestone itself
603
+ const finalPath = await deps.moveToArchive(entity.id, archivePath);
604
+ if (remove_from_canvas && deps.removeFromCanvas && canvas_source) {
605
+ await deps.removeFromCanvas(entity.id, canvas_source);
606
+ }
607
+ return {
608
+ archived: true,
609
+ archive_path: finalPath,
610
+ archived_children: archivedChildren,
611
+ };
612
+ }
613
+ // For non-cascade archive, check for children
614
+ if (!force) {
615
+ const children = await deps.getChildren(entity.id);
616
+ if (children.length > 0) {
617
+ throw new Error(`Entity has ${children.length} children. Use archive_options.force=true to archive anyway, or archive_options.cascade=true to archive children.`);
618
+ }
619
+ }
620
+ // Move to archive
621
+ const finalPath = await deps.moveToArchive(entity.id, archive_folder);
622
+ // Remove from canvas if requested
623
+ if (remove_from_canvas && deps.removeFromCanvas && canvas_source) {
624
+ await deps.removeFromCanvas(entity.id, canvas_source);
625
+ }
626
+ return {
627
+ archived: true,
628
+ archive_path: finalPath,
629
+ };
630
+ }
631
+ // =============================================================================
632
+ // Helper: Handle Restore Operation
633
+ // =============================================================================
634
+ async function handleRestoreOperation(entity, options, deps) {
635
+ const { restore_children, add_to_canvas, canvas_source } = options || {};
636
+ // Restore the entity
637
+ await deps.restoreFromArchive(entity.id);
638
+ // Add to canvas if requested
639
+ if (add_to_canvas && deps.addToCanvas && canvas_source) {
640
+ const restoredEntity = await deps.getEntity(entity.id);
641
+ if (restoredEntity) {
642
+ await deps.addToCanvas(restoredEntity, canvas_source);
643
+ }
644
+ }
645
+ // Restore children if requested
646
+ const restoredChildren = [];
647
+ if (restore_children) {
648
+ const children = await deps.getChildren(entity.id);
649
+ for (const child of children) {
650
+ await deps.restoreFromArchive(child.id);
651
+ restoredChildren.push(child.id);
652
+ if (add_to_canvas && deps.addToCanvas && canvas_source) {
653
+ await deps.addToCanvas(child, canvas_source);
654
+ }
655
+ // Also restore grandchildren (tasks under stories)
656
+ const grandchildren = await deps.getChildren(child.id);
657
+ for (const grandchild of grandchildren) {
658
+ await deps.restoreFromArchive(grandchild.id);
659
+ restoredChildren.push(grandchild.id);
660
+ if (add_to_canvas && deps.addToCanvas && canvas_source) {
661
+ await deps.addToCanvas(grandchild, canvas_source);
662
+ }
663
+ }
664
+ }
665
+ }
666
+ return {
667
+ restored: true,
668
+ restored_children: restoredChildren.length > 0 ? restoredChildren : undefined,
669
+ };
670
+ }
671
+ // =============================================================================
672
+ // Update Entity Status (DEPRECATED - use updateEntity with status field)
673
+ // =============================================================================
674
+ /**
675
+ * @deprecated Use updateEntity({ id, status, status_note, cascade }) instead.
676
+ * Dedicated status update with optional note and cascade.
677
+ */
678
+ export async function updateEntityStatus(input, deps) {
679
+ const { id, status, note, cascade } = input;
680
+ // Get existing entity
681
+ const entity = await deps.getEntity(id);
682
+ if (!entity) {
683
+ throw new Error(`Entity not found: ${id}`);
684
+ }
685
+ const oldStatus = entity.status;
686
+ // Validate transition
687
+ const validation = deps.validateStatusTransition(entity, status);
688
+ if (!validation.valid) {
689
+ throw new Error(`Invalid status transition: ${validation.reason}`);
690
+ }
691
+ // Update entity - cast to Entity to handle status type variance
692
+ const updatedEntity = {
693
+ ...entity,
694
+ status,
695
+ updated_at: deps.getCurrentTimestamp(),
696
+ };
697
+ // Add note to content if provided
698
+ if (note) {
699
+ // Note: In a real implementation, this would append to the entity's notes section
700
+ // For now, we just update the entity
701
+ }
702
+ // Write updated entity
703
+ await deps.writeEntity(updatedEntity);
704
+ // Compute cascade effects if requested
705
+ let cascadedUpdates = [];
706
+ if (cascade) {
707
+ cascadedUpdates = await deps.computeCascadeEffects(updatedEntity, status);
708
+ }
709
+ return {
710
+ id,
711
+ old_status: oldStatus,
712
+ new_status: status,
713
+ cascaded_updates: cascadedUpdates,
714
+ };
715
+ }
716
+ // =============================================================================
717
+ // Archive Entity (DEPRECATED - use updateEntity with archived: true)
718
+ // =============================================================================
719
+ /**
720
+ * @deprecated Use updateEntity({ id, archived: true, archive_options }) instead.
721
+ * Archive a single entity.
722
+ */
723
+ export async function archiveEntity(input, deps) {
724
+ const { id, force, remove_from_canvas, canvas_source } = input;
725
+ // Get existing entity
726
+ const entity = await deps.getEntity(id);
727
+ if (!entity) {
728
+ throw new Error(`Entity not found: ${id}`);
729
+ }
730
+ // Check for children if not forcing
731
+ if (!force) {
732
+ const children = await deps.getChildren(id);
733
+ if (children.length > 0) {
734
+ throw new Error(`Entity has ${children.length} children. Use force=true to archive anyway.`);
735
+ }
736
+ }
737
+ // Move to archive (runtime handles path computation based on config)
738
+ const finalPath = await deps.moveToArchive(id);
739
+ // Remove from canvas if requested
740
+ if (remove_from_canvas && deps.removeFromCanvas && canvas_source) {
741
+ await deps.removeFromCanvas(id, canvas_source);
742
+ }
743
+ return {
744
+ id,
745
+ archived: true,
746
+ archive_path: finalPath,
747
+ };
748
+ }
749
+ // =============================================================================
750
+ // Archive Milestone (DEPRECATED - use updateEntity with archived: true, archive_options.cascade: true)
751
+ // =============================================================================
752
+ /**
753
+ * @deprecated Use updateEntity({ id, archived: true, archive_options: { cascade: true } }) instead.
754
+ * Archive a milestone and all its children.
755
+ */
756
+ export async function archiveMilestone(input, deps) {
757
+ const { milestone_id, archive_folder, remove_from_canvas, canvas_source } = input;
758
+ // Get milestone
759
+ const milestone = await deps.getEntity(milestone_id);
760
+ if (!milestone) {
761
+ throw new Error(`Milestone not found: ${milestone_id}`);
762
+ }
763
+ if (milestone.type !== 'milestone') {
764
+ throw new Error(`Entity ${milestone_id} is not a milestone`);
765
+ }
766
+ // Compute archive path
767
+ const now = new Date();
768
+ const quarter = Math.ceil((now.getMonth() + 1) / 3);
769
+ const archivePath = archive_folder || `archive/${now.getFullYear()}-Q${quarter}`;
770
+ // Collect all entities to archive
771
+ const archivedMilestones = [milestone_id];
772
+ const archivedStories = [];
773
+ const archivedTasks = [];
774
+ // Get all children (stories)
775
+ const stories = await deps.getChildren(milestone_id);
776
+ for (const story of stories) {
777
+ archivedStories.push(story.id);
778
+ // Get tasks for each story
779
+ const tasks = await deps.getChildren(story.id);
780
+ for (const task of tasks) {
781
+ archivedTasks.push(task.id);
782
+ }
783
+ }
784
+ // Archive all entities and remove from canvas
785
+ const allIds = [...archivedTasks, ...archivedStories, ...archivedMilestones];
786
+ for (const id of allIds) {
787
+ await deps.moveToArchive(id, archivePath);
788
+ // Remove from canvas if requested
789
+ if (remove_from_canvas && deps.removeFromCanvas && canvas_source) {
790
+ await deps.removeFromCanvas(id, canvas_source);
791
+ }
792
+ }
793
+ return {
794
+ milestone_id,
795
+ archived_entities: {
796
+ milestones: archivedMilestones,
797
+ stories: archivedStories,
798
+ tasks: archivedTasks,
799
+ },
800
+ total_archived: archivedMilestones.length + archivedStories.length + archivedTasks.length,
801
+ archive_path: archivePath,
802
+ };
803
+ }
804
+ // =============================================================================
805
+ // Restore From Archive (DEPRECATED - use updateEntity with archived: false)
806
+ // =============================================================================
807
+ /**
808
+ * @deprecated Use updateEntity({ id, archived: false, restore_options }) instead.
809
+ * Restore an archived entity.
810
+ */
811
+ export async function restoreFromArchive(input, deps) {
812
+ const { id, restore_children, add_to_canvas, canvas_source } = input;
813
+ // Restore the entity
814
+ await deps.restoreFromArchive(id);
815
+ // Add to canvas if requested
816
+ if (add_to_canvas && deps.addToCanvas && canvas_source) {
817
+ const entity = await deps.getEntity(id);
818
+ if (entity) {
819
+ await deps.addToCanvas(entity, canvas_source);
820
+ }
821
+ }
822
+ // Restore children if requested
823
+ const restoredChildren = [];
824
+ if (restore_children) {
825
+ const children = await deps.getChildren(id);
826
+ for (const child of children) {
827
+ await deps.restoreFromArchive(child.id);
828
+ restoredChildren.push(child.id);
829
+ // Add child to canvas if requested
830
+ if (add_to_canvas && deps.addToCanvas && canvas_source) {
831
+ await deps.addToCanvas(child, canvas_source);
832
+ }
833
+ // Also restore grandchildren (tasks under stories)
834
+ const grandchildren = await deps.getChildren(child.id);
835
+ for (const grandchild of grandchildren) {
836
+ await deps.restoreFromArchive(grandchild.id);
837
+ restoredChildren.push(grandchild.id);
838
+ // Add grandchild to canvas if requested
839
+ if (add_to_canvas && deps.addToCanvas && canvas_source) {
840
+ await deps.addToCanvas(grandchild, canvas_source);
841
+ }
842
+ }
843
+ }
844
+ }
845
+ return {
846
+ id,
847
+ restored: true,
848
+ restored_children: restoredChildren,
849
+ };
850
+ }
851
+ //# sourceMappingURL=entity-management-tools.js.map