octie-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +523 -0
  2. package/dist/cli/commands/approve.d.ts +27 -0
  3. package/dist/cli/commands/approve.d.ts.map +1 -0
  4. package/dist/cli/commands/approve.js +119 -0
  5. package/dist/cli/commands/approve.js.map +1 -0
  6. package/dist/cli/commands/batch.d.ts +15 -0
  7. package/dist/cli/commands/batch.d.ts.map +1 -0
  8. package/dist/cli/commands/batch.js +521 -0
  9. package/dist/cli/commands/batch.js.map +1 -0
  10. package/dist/cli/commands/create.d.ts +9 -0
  11. package/dist/cli/commands/create.d.ts.map +1 -0
  12. package/dist/cli/commands/create.js +321 -0
  13. package/dist/cli/commands/create.js.map +1 -0
  14. package/dist/cli/commands/delete.d.ts +9 -0
  15. package/dist/cli/commands/delete.d.ts.map +1 -0
  16. package/dist/cli/commands/delete.js +143 -0
  17. package/dist/cli/commands/delete.js.map +1 -0
  18. package/dist/cli/commands/export.d.ts +9 -0
  19. package/dist/cli/commands/export.d.ts.map +1 -0
  20. package/dist/cli/commands/export.js +66 -0
  21. package/dist/cli/commands/export.js.map +1 -0
  22. package/dist/cli/commands/find.d.ts +16 -0
  23. package/dist/cli/commands/find.d.ts.map +1 -0
  24. package/dist/cli/commands/find.js +252 -0
  25. package/dist/cli/commands/find.js.map +1 -0
  26. package/dist/cli/commands/get.d.ts +9 -0
  27. package/dist/cli/commands/get.d.ts.map +1 -0
  28. package/dist/cli/commands/get.js +74 -0
  29. package/dist/cli/commands/get.js.map +1 -0
  30. package/dist/cli/commands/graph.d.ts +9 -0
  31. package/dist/cli/commands/graph.d.ts.map +1 -0
  32. package/dist/cli/commands/graph.js +200 -0
  33. package/dist/cli/commands/graph.js.map +1 -0
  34. package/dist/cli/commands/import.d.ts +9 -0
  35. package/dist/cli/commands/import.d.ts.map +1 -0
  36. package/dist/cli/commands/import.js +807 -0
  37. package/dist/cli/commands/import.js.map +1 -0
  38. package/dist/cli/commands/init.d.ts +9 -0
  39. package/dist/cli/commands/init.d.ts.map +1 -0
  40. package/dist/cli/commands/init.js +57 -0
  41. package/dist/cli/commands/init.js.map +1 -0
  42. package/dist/cli/commands/list.d.ts +9 -0
  43. package/dist/cli/commands/list.d.ts.map +1 -0
  44. package/dist/cli/commands/list.js +175 -0
  45. package/dist/cli/commands/list.js.map +1 -0
  46. package/dist/cli/commands/merge.d.ts +9 -0
  47. package/dist/cli/commands/merge.d.ts.map +1 -0
  48. package/dist/cli/commands/merge.js +113 -0
  49. package/dist/cli/commands/merge.js.map +1 -0
  50. package/dist/cli/commands/serve.d.ts +9 -0
  51. package/dist/cli/commands/serve.d.ts.map +1 -0
  52. package/dist/cli/commands/serve.js +94 -0
  53. package/dist/cli/commands/serve.js.map +1 -0
  54. package/dist/cli/commands/update.d.ts +9 -0
  55. package/dist/cli/commands/update.d.ts.map +1 -0
  56. package/dist/cli/commands/update.js +423 -0
  57. package/dist/cli/commands/update.js.map +1 -0
  58. package/dist/cli/commands/wire.d.ts +15 -0
  59. package/dist/cli/commands/wire.d.ts.map +1 -0
  60. package/dist/cli/commands/wire.js +164 -0
  61. package/dist/cli/commands/wire.js.map +1 -0
  62. package/dist/cli/index.d.ts +7 -0
  63. package/dist/cli/index.d.ts.map +1 -0
  64. package/dist/cli/index.js +100 -0
  65. package/dist/cli/index.js.map +1 -0
  66. package/dist/cli/output/json.d.ts +16 -0
  67. package/dist/cli/output/json.d.ts.map +1 -0
  68. package/dist/cli/output/json.js +29 -0
  69. package/dist/cli/output/json.js.map +1 -0
  70. package/dist/cli/output/markdown.d.ts +15 -0
  71. package/dist/cli/output/markdown.d.ts.map +1 -0
  72. package/dist/cli/output/markdown.js +206 -0
  73. package/dist/cli/output/markdown.js.map +1 -0
  74. package/dist/cli/output/table.d.ts +23 -0
  75. package/dist/cli/output/table.d.ts.map +1 -0
  76. package/dist/cli/output/table.js +150 -0
  77. package/dist/cli/output/table.js.map +1 -0
  78. package/dist/cli/utils/helpers.d.ts +126 -0
  79. package/dist/cli/utils/helpers.d.ts.map +1 -0
  80. package/dist/cli/utils/helpers.js +325 -0
  81. package/dist/cli/utils/helpers.js.map +1 -0
  82. package/dist/core/graph/algorithms.d.ts +11 -0
  83. package/dist/core/graph/algorithms.d.ts.map +1 -0
  84. package/dist/core/graph/algorithms.js +14 -0
  85. package/dist/core/graph/algorithms.js.map +1 -0
  86. package/dist/core/graph/cycle.d.ts +155 -0
  87. package/dist/core/graph/cycle.d.ts.map +1 -0
  88. package/dist/core/graph/cycle.js +297 -0
  89. package/dist/core/graph/cycle.js.map +1 -0
  90. package/dist/core/graph/index.d.ts +223 -0
  91. package/dist/core/graph/index.d.ts.map +1 -0
  92. package/dist/core/graph/index.js +475 -0
  93. package/dist/core/graph/index.js.map +1 -0
  94. package/dist/core/graph/operations.d.ts +240 -0
  95. package/dist/core/graph/operations.d.ts.map +1 -0
  96. package/dist/core/graph/operations.js +503 -0
  97. package/dist/core/graph/operations.js.map +1 -0
  98. package/dist/core/graph/sort.d.ts +76 -0
  99. package/dist/core/graph/sort.d.ts.map +1 -0
  100. package/dist/core/graph/sort.js +254 -0
  101. package/dist/core/graph/sort.js.map +1 -0
  102. package/dist/core/graph/traversal.d.ts +122 -0
  103. package/dist/core/graph/traversal.d.ts.map +1 -0
  104. package/dist/core/graph/traversal.js +336 -0
  105. package/dist/core/graph/traversal.js.map +1 -0
  106. package/dist/core/models/task-node.d.ts +328 -0
  107. package/dist/core/models/task-node.d.ts.map +1 -0
  108. package/dist/core/models/task-node.js +1090 -0
  109. package/dist/core/models/task-node.js.map +1 -0
  110. package/dist/core/registry/index.d.ts +102 -0
  111. package/dist/core/registry/index.d.ts.map +1 -0
  112. package/dist/core/registry/index.js +249 -0
  113. package/dist/core/registry/index.js.map +1 -0
  114. package/dist/core/registry/root-guard.d.ts +19 -0
  115. package/dist/core/registry/root-guard.d.ts.map +1 -0
  116. package/dist/core/registry/root-guard.js +28 -0
  117. package/dist/core/registry/root-guard.js.map +1 -0
  118. package/dist/core/storage/atomic-write.d.ts +181 -0
  119. package/dist/core/storage/atomic-write.d.ts.map +1 -0
  120. package/dist/core/storage/atomic-write.js +379 -0
  121. package/dist/core/storage/atomic-write.js.map +1 -0
  122. package/dist/core/storage/file-store.d.ts +148 -0
  123. package/dist/core/storage/file-store.d.ts.map +1 -0
  124. package/dist/core/storage/file-store.js +423 -0
  125. package/dist/core/storage/file-store.js.map +1 -0
  126. package/dist/core/storage/indexer.d.ts +138 -0
  127. package/dist/core/storage/indexer.d.ts.map +1 -0
  128. package/dist/core/storage/indexer.js +350 -0
  129. package/dist/core/storage/indexer.js.map +1 -0
  130. package/dist/core/utils/status-helpers.d.ts +59 -0
  131. package/dist/core/utils/status-helpers.d.ts.map +1 -0
  132. package/dist/core/utils/status-helpers.js +149 -0
  133. package/dist/core/utils/status-helpers.js.map +1 -0
  134. package/dist/index.d.ts +10 -0
  135. package/dist/index.d.ts.map +1 -0
  136. package/dist/index.js +10 -0
  137. package/dist/index.js.map +1 -0
  138. package/dist/types/index.d.ts +504 -0
  139. package/dist/types/index.d.ts.map +1 -0
  140. package/dist/types/index.js +182 -0
  141. package/dist/types/index.js.map +1 -0
  142. package/dist/web/routes/graph.d.ts +17 -0
  143. package/dist/web/routes/graph.d.ts.map +1 -0
  144. package/dist/web/routes/graph.js +277 -0
  145. package/dist/web/routes/graph.js.map +1 -0
  146. package/dist/web/routes/projects.d.ts +14 -0
  147. package/dist/web/routes/projects.d.ts.map +1 -0
  148. package/dist/web/routes/projects.js +102 -0
  149. package/dist/web/routes/projects.js.map +1 -0
  150. package/dist/web/routes/tasks.d.ts +17 -0
  151. package/dist/web/routes/tasks.d.ts.map +1 -0
  152. package/dist/web/routes/tasks.js +538 -0
  153. package/dist/web/routes/tasks.js.map +1 -0
  154. package/dist/web/server.d.ts +121 -0
  155. package/dist/web/server.d.ts.map +1 -0
  156. package/dist/web/server.js +389 -0
  157. package/dist/web/server.js.map +1 -0
  158. package/dist/web-ui/assets/index-BB0qvF1y.css +1 -0
  159. package/dist/web-ui/assets/index-Vmm72oKY.js +34 -0
  160. package/dist/web-ui/index.html +14 -0
  161. package/dist/web-ui/vite.svg +1 -0
  162. package/package.json +94 -0
@@ -0,0 +1,807 @@
1
+ /**
2
+ * Import command - Import project data from file
3
+ */
4
+ import { Command } from 'commander';
5
+ import { TaskStorage } from '../../core/storage/file-store.js';
6
+ import { getProjectPath, success, error, warning } from '../utils/helpers.js';
7
+ import chalk from 'chalk';
8
+ import { readFileSync, existsSync } from 'node:fs';
9
+ import { TaskGraphStore } from '../../core/graph/index.js';
10
+ import { TaskNode } from '../../core/models/task-node.js';
11
+ import { resolve } from 'node:path';
12
+ import { v4 as uuidv4 } from 'uuid';
13
+ /**
14
+ * Auto-detect format from file extension
15
+ */
16
+ function detectFormat(filePath) {
17
+ const ext = filePath.toLowerCase().split('.').pop();
18
+ if (ext === 'json')
19
+ return 'json';
20
+ if (ext === 'md' || ext === 'markdown')
21
+ return 'md';
22
+ return null;
23
+ }
24
+ /**
25
+ * Parse checkbox state from markdown
26
+ * Supports: [x], [X], [ ], [y], [Y], etc.
27
+ */
28
+ function parseCheckbox(text) {
29
+ const checkboxMatch = text.match(/^\s*\[([ xXyY])\]\s*(.*)$/);
30
+ if (checkboxMatch && checkboxMatch[1] !== undefined && checkboxMatch[2] !== undefined) {
31
+ const checked = checkboxMatch[1].toLowerCase() === 'x' || checkboxMatch[1].toLowerCase() === 'y';
32
+ return { checked, content: checkboxMatch[2].trim() };
33
+ }
34
+ return { checked: false, content: text.trim() };
35
+ }
36
+ /**
37
+ * Extract task ID from line like "**ID**: `task-123`"
38
+ */
39
+ function extractTaskId(lines, startIndex) {
40
+ for (let i = startIndex; i < Math.min(startIndex + 5, lines.length); i++) {
41
+ const line = lines[i];
42
+ if (line === undefined)
43
+ continue;
44
+ const idMatch = line.match(/\*\*ID\*\*:\s*`([^`]+)`/);
45
+ if (idMatch && idMatch[1]) {
46
+ return idMatch[1];
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ /**
52
+ * Extract status from line like "**Status**: in_progress" or "**Status**: in progress"
53
+ */
54
+ function extractStatus(lines, startIndex) {
55
+ for (let i = startIndex; i < Math.min(startIndex + 5, lines.length); i++) {
56
+ const line = lines[i];
57
+ if (line === undefined)
58
+ continue;
59
+ // Match both underscore format (in_progress) and space format (in progress)
60
+ const statusMatch = line.match(/\*\*Status\*\*:\s*([\w\s]+?)(?:\s*\||\s*$)/);
61
+ if (statusMatch && statusMatch[1]) {
62
+ // Normalize: convert spaces to underscores, trim
63
+ const status = statusMatch[1].toLowerCase().trim().replace(/\s+/g, '_');
64
+ // Support both old and new status values for backward compatibility
65
+ if (['ready', 'in_progress', 'in_review', 'completed', 'blocked'].includes(status)) {
66
+ return status;
67
+ }
68
+ // Migrate old statuses to new ones
69
+ if (status === 'not_started' || status === 'pending') {
70
+ return 'ready';
71
+ }
72
+ }
73
+ }
74
+ return 'ready';
75
+ }
76
+ /**
77
+ * Extract priority from line like "**Priority**: top"
78
+ */
79
+ function extractPriority(lines, startIndex) {
80
+ for (let i = startIndex; i < Math.min(startIndex + 5, lines.length); i++) {
81
+ const line = lines[i];
82
+ if (line === undefined)
83
+ continue;
84
+ const priorityMatch = line.match(/\*\*Priority\*\*:\s*(\w+)/);
85
+ if (priorityMatch && priorityMatch[1]) {
86
+ const priority = priorityMatch[1].toLowerCase();
87
+ if (['top', 'second', 'later'].includes(priority)) {
88
+ return priority;
89
+ }
90
+ }
91
+ }
92
+ return 'second';
93
+ }
94
+ /**
95
+ * Extract task reference (blocker/dependency) from line like "- #task-123"
96
+ * Supports UUIDs, custom IDs with alphanumerics and hyphens
97
+ */
98
+ function extractTaskReference(line) {
99
+ const refMatch = line.match(/^-\s*#([\w-]+)$/);
100
+ if (refMatch && refMatch[1]) {
101
+ return refMatch[1];
102
+ }
103
+ return null;
104
+ }
105
+ /**
106
+ * Extract file path from line like "- \`src/file.ts\`"
107
+ */
108
+ function extractFilePath(line) {
109
+ const fileMatch = line.match(/^-\s*`([^`]+)`$/);
110
+ if (fileMatch && fileMatch[1]) {
111
+ return fileMatch[1];
112
+ }
113
+ return null;
114
+ }
115
+ /**
116
+ * Parse markdown content into task array
117
+ *
118
+ * Supports format:
119
+ * ## [x] Task Title
120
+ * **ID**: `task-id` | **Status**: in_progress | **Priority**: top
121
+ *
122
+ * ### Description
123
+ * Task description here...
124
+ *
125
+ * ### Success Criteria
126
+ * - [x] Criterion 1
127
+ * - [ ] Criterion 2
128
+ *
129
+ * ### Deliverables
130
+ * - [ ] Deliverable 1 → `file.ts`
131
+ *
132
+ * ### Blockers
133
+ * - #blocker-task-id
134
+ *
135
+ * ### Notes
136
+ * Additional notes...
137
+ */
138
+ function parseMarkdownTasks(content) {
139
+ const tasks = [];
140
+ const lines = content.split('\n');
141
+ let i = 0;
142
+ while (i < lines.length) {
143
+ const line = lines[i];
144
+ if (line === undefined) {
145
+ i++;
146
+ continue;
147
+ }
148
+ const trimmedLine = line.trim();
149
+ // Look for task header: ## [x] Title or ## [ ] Title
150
+ const taskHeaderMatch = trimmedLine.match(/^##\s*\[([ xX])\]\s+(.+)$/);
151
+ if (taskHeaderMatch && taskHeaderMatch[1] && taskHeaderMatch[2]) {
152
+ const completed = taskHeaderMatch[1].toLowerCase() === 'x';
153
+ const title = taskHeaderMatch[2].trim();
154
+ // Find task end (next task header or end of file)
155
+ let taskEndIndex = lines.length;
156
+ for (let j = i + 1; j < lines.length; j++) {
157
+ const nextLine = lines[j];
158
+ if (nextLine && nextLine.trim().match(/^##\s*\[[ xX]\]/)) {
159
+ taskEndIndex = j;
160
+ break;
161
+ }
162
+ }
163
+ // Extract task metadata
164
+ const taskId = extractTaskId(lines, i) || uuidv4();
165
+ const status = completed ? 'completed' : extractStatus(lines, i);
166
+ const priority = extractPriority(lines, i);
167
+ // Extract description
168
+ let description = '';
169
+ let descStart = -1;
170
+ for (let j = i; j < taskEndIndex; j++) {
171
+ const l = lines[j];
172
+ if (l && l.trim() === '### Description') {
173
+ descStart = j + 1;
174
+ break;
175
+ }
176
+ }
177
+ if (descStart > 0) {
178
+ const descLines = [];
179
+ for (let j = descStart; j < taskEndIndex; j++) {
180
+ const l = lines[j];
181
+ if (l === undefined)
182
+ continue;
183
+ const lt = l.trim();
184
+ if (lt.startsWith('### ') || lt === '---')
185
+ break;
186
+ if (lt)
187
+ descLines.push(l); // Preserve original formatting
188
+ }
189
+ description = descLines.join('\n').trim();
190
+ }
191
+ // Extract success criteria
192
+ const success_criteria = [];
193
+ for (let j = i; j < taskEndIndex; j++) {
194
+ const l = lines[j];
195
+ if (l === undefined)
196
+ continue;
197
+ if (l.trim() === '### Success Criteria') {
198
+ let k = j + 1;
199
+ while (k < taskEndIndex) {
200
+ const itemLine = lines[k];
201
+ if (itemLine === undefined) {
202
+ k++;
203
+ continue;
204
+ }
205
+ const lt = itemLine.trim();
206
+ if (lt.startsWith('### ') || lt === '---')
207
+ break;
208
+ // Skip timestamp lines like " - Completed: 2026-02-16T18:11:52.088Z"
209
+ if (lt.match(/^\s*-\s*Completed:\s*\d{4}-\d{2}-\d{2}T/)) {
210
+ k++;
211
+ continue;
212
+ }
213
+ if (lt.startsWith('- ')) {
214
+ const { checked, content } = parseCheckbox(lt.substring(2));
215
+ if (content) {
216
+ // Extract full UUID from end of content (format: "text \`uuid\`")
217
+ // UUID pattern: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
218
+ const uuidMatch = content.match(/^(.+?)\s*`([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})`$/);
219
+ let id = uuidv4();
220
+ let text = content;
221
+ if (uuidMatch && uuidMatch[1] && uuidMatch[2]) {
222
+ text = uuidMatch[1].trim();
223
+ id = uuidMatch[2];
224
+ }
225
+ success_criteria.push({
226
+ id,
227
+ text,
228
+ completed: checked,
229
+ completed_at: checked ? new Date().toISOString() : undefined,
230
+ });
231
+ }
232
+ }
233
+ k++;
234
+ }
235
+ break;
236
+ }
237
+ }
238
+ // Extract deliverables
239
+ const deliverables = [];
240
+ for (let j = i; j < taskEndIndex; j++) {
241
+ const l = lines[j];
242
+ if (l === undefined)
243
+ continue;
244
+ if (l.trim() === '### Deliverables') {
245
+ let k = j + 1;
246
+ while (k < taskEndIndex) {
247
+ const itemLine = lines[k];
248
+ if (itemLine === undefined) {
249
+ k++;
250
+ continue;
251
+ }
252
+ const lt = itemLine.trim();
253
+ if (lt.startsWith('### ') || lt === '---')
254
+ break;
255
+ // Skip timestamp lines like " - Completed: 2026-02-16T18:11:52.088Z"
256
+ if (lt.match(/^\s*-\s*Completed:\s*\d{4}-\d{2}-\d{2}T/)) {
257
+ k++;
258
+ continue;
259
+ }
260
+ if (lt.startsWith('- ')) {
261
+ const itemText = lt.substring(2).trim();
262
+ const { checked, content } = parseCheckbox(itemText);
263
+ // Extract full UUID from end of content (format: "text → `filepath` `uuid`" or "text `uuid`")
264
+ let id = uuidv4();
265
+ let text = content;
266
+ let filePath;
267
+ // First try to extract UUID from end
268
+ const uuidMatch = content.match(/^(.+?)\s*`([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})`$/);
269
+ if (uuidMatch && uuidMatch[1] && uuidMatch[2]) {
270
+ text = uuidMatch[1].trim();
271
+ id = uuidMatch[2];
272
+ }
273
+ // Then check for file path: "text → `file.ts`"
274
+ const fileMatch = text.match(/^(.+?)\s*→\s*`([^`]+)`$/);
275
+ if (fileMatch && fileMatch[1] && fileMatch[2]) {
276
+ text = fileMatch[1].trim();
277
+ filePath = fileMatch[2];
278
+ }
279
+ if (text) {
280
+ deliverables.push({
281
+ id,
282
+ text,
283
+ completed: checked,
284
+ file_path: filePath,
285
+ });
286
+ }
287
+ }
288
+ k++;
289
+ }
290
+ break;
291
+ }
292
+ }
293
+ // Extract blockers
294
+ const blockers = [];
295
+ for (let j = i; j < taskEndIndex; j++) {
296
+ const l = lines[j];
297
+ if (l === undefined)
298
+ continue;
299
+ if (l.trim() === '### Blockers') {
300
+ let k = j + 1;
301
+ while (k < taskEndIndex) {
302
+ const itemLine = lines[k];
303
+ if (itemLine === undefined) {
304
+ k++;
305
+ continue;
306
+ }
307
+ const lt = itemLine.trim();
308
+ if (lt.startsWith('### ') || lt === '---')
309
+ break;
310
+ const ref = extractTaskReference(lt);
311
+ if (ref)
312
+ blockers.push(ref);
313
+ k++;
314
+ }
315
+ break;
316
+ }
317
+ }
318
+ // Extract dependencies (explanatory text - twin to blockers)
319
+ let dependencies = '';
320
+ for (let j = i; j < taskEndIndex; j++) {
321
+ const l = lines[j];
322
+ if (l === undefined)
323
+ continue;
324
+ if (l.trim() === '### Dependencies') {
325
+ const depLines = [];
326
+ let k = j + 1;
327
+ while (k < taskEndIndex) {
328
+ const itemLine = lines[k];
329
+ if (itemLine === undefined) {
330
+ k++;
331
+ continue;
332
+ }
333
+ const lt = itemLine.trim();
334
+ if (lt.startsWith('### ') || lt === '---')
335
+ break;
336
+ depLines.push(itemLine);
337
+ k++;
338
+ }
339
+ dependencies = depLines.join('\n').trim();
340
+ break;
341
+ }
342
+ }
343
+ // Extract related files
344
+ const related_files = [];
345
+ for (let j = i; j < taskEndIndex; j++) {
346
+ const l = lines[j];
347
+ if (l === undefined)
348
+ continue;
349
+ if (l.trim() === '### Related Files') {
350
+ let k = j + 1;
351
+ while (k < taskEndIndex) {
352
+ const itemLine = lines[k];
353
+ if (itemLine === undefined) {
354
+ k++;
355
+ continue;
356
+ }
357
+ const lt = itemLine.trim();
358
+ if (lt.startsWith('### ') || lt === '---')
359
+ break;
360
+ const file = extractFilePath(lt);
361
+ if (file)
362
+ related_files.push(file);
363
+ k++;
364
+ }
365
+ break;
366
+ }
367
+ }
368
+ // Extract notes
369
+ let notes = '';
370
+ for (let j = i; j < taskEndIndex; j++) {
371
+ const l = lines[j];
372
+ if (l === undefined)
373
+ continue;
374
+ if (l.trim() === '### Notes') {
375
+ const noteLines = [];
376
+ let k = j + 1;
377
+ while (k < taskEndIndex) {
378
+ const noteLine = lines[k];
379
+ if (noteLine === undefined) {
380
+ k++;
381
+ continue;
382
+ }
383
+ const lt = noteLine.trim();
384
+ if (lt.startsWith('### ') || lt === '---')
385
+ break;
386
+ noteLines.push(noteLine);
387
+ k++;
388
+ }
389
+ notes = noteLines.join('\n').trim();
390
+ break;
391
+ }
392
+ }
393
+ // Extract C7 verifications (Library Verifications section)
394
+ const c7_verified = [];
395
+ for (let j = i; j < taskEndIndex; j++) {
396
+ const l = lines[j];
397
+ if (l === undefined)
398
+ continue;
399
+ if (l.trim() === '### Library Verifications') {
400
+ let k = j + 1;
401
+ while (k < taskEndIndex) {
402
+ const itemLine = lines[k];
403
+ if (itemLine === undefined) {
404
+ k++;
405
+ continue;
406
+ }
407
+ const lt = itemLine.trim();
408
+ if (lt.startsWith('### ') || lt === '---')
409
+ break;
410
+ // Parse: "- /library/id (verified: 2026-02-16T18:11:52.088Z)"
411
+ const c7Match = lt.match(/^-?\s*([\/\w.-]+)\s*\(verified:\s*(\d{4}-\d{2}-\d{2}T[\d:.Z]+)\)$/);
412
+ if (c7Match && c7Match[1] && c7Match[2]) {
413
+ const library_id = c7Match[1];
414
+ const verified_at = c7Match[2];
415
+ // Look for optional notes on next line(s) (indented with " - ")
416
+ let c7Notes;
417
+ const noteLines = [];
418
+ let nextK = k + 1;
419
+ while (nextK < taskEndIndex) {
420
+ const nextLine = lines[nextK];
421
+ if (nextLine === undefined)
422
+ break;
423
+ const nextLt = nextLine.trim();
424
+ // Check for indented note: " - note text"
425
+ if (nextLt.startsWith('- ') && nextLine.startsWith(' ')) {
426
+ noteLines.push(nextLt.substring(2).trim());
427
+ nextK++;
428
+ }
429
+ else if (nextLt.startsWith('-') || nextLt.startsWith('### ') || nextLt === '---') {
430
+ // Stop at next item or section
431
+ break;
432
+ }
433
+ else {
434
+ break;
435
+ }
436
+ }
437
+ if (noteLines.length > 0) {
438
+ c7Notes = noteLines.join(' ');
439
+ k = nextK - 1; // Update k to skip processed note lines
440
+ }
441
+ c7_verified.push({
442
+ library_id,
443
+ verified_at,
444
+ notes: c7Notes,
445
+ });
446
+ }
447
+ k++;
448
+ }
449
+ break;
450
+ }
451
+ }
452
+ // Create task with default required fields if missing
453
+ const task = {
454
+ id: taskId,
455
+ title: title || 'Untitled Task',
456
+ description: description || 'Imported from markdown file without description. Please add a detailed description.',
457
+ status,
458
+ priority,
459
+ success_criteria: success_criteria.length > 0 ? success_criteria : [
460
+ { id: uuidv4(), text: 'Task imported from markdown', completed: false },
461
+ ],
462
+ deliverables: deliverables.length > 0 ? deliverables : [
463
+ { id: uuidv4(), text: 'Deliverable to be defined', completed: false },
464
+ ],
465
+ blockers,
466
+ dependencies,
467
+ sub_items: [],
468
+ related_files,
469
+ notes,
470
+ c7_verified,
471
+ completed,
472
+ };
473
+ tasks.push(task);
474
+ i = taskEndIndex;
475
+ }
476
+ else {
477
+ i++;
478
+ }
479
+ }
480
+ return tasks;
481
+ }
482
+ /**
483
+ * Validate imported data structure
484
+ */
485
+ function validateImportData(data) {
486
+ if (!data || typeof data !== 'object') {
487
+ throw new Error('Invalid data: must be an object');
488
+ }
489
+ const d = data;
490
+ // Check for tasks format (from project file export)
491
+ if (d.version && d.format && d.tasks) {
492
+ // Full project file format
493
+ if (!d.tasks || typeof d.tasks !== 'object') {
494
+ throw new Error('Invalid project file: missing or invalid tasks object');
495
+ }
496
+ return;
497
+ }
498
+ // Check for nodes format (from toJSON() export)
499
+ if (d.nodes && typeof d.nodes === 'object') {
500
+ return;
501
+ }
502
+ // Check for simple task array format
503
+ if (Array.isArray(data)) {
504
+ for (const task of data) {
505
+ if (!task.id || !task.title) {
506
+ throw new Error('Invalid task data: missing id or title');
507
+ }
508
+ }
509
+ return;
510
+ }
511
+ // Single task format
512
+ if (d.id && d.title) {
513
+ return;
514
+ }
515
+ throw new Error('Unrecognized data format. Expected project file, task array, or single task.');
516
+ }
517
+ /**
518
+ * Create TaskGraphStore from various import formats
519
+ */
520
+ function createGraphFromImportData(data) {
521
+ const d = data;
522
+ // Handle nodes format (from toJSON() export)
523
+ if (d.nodes && d.outgoingEdges !== undefined && d.incomingEdges !== undefined) {
524
+ return TaskGraphStore.fromJSON(d);
525
+ }
526
+ // Handle tasks format (from project file export)
527
+ if (d.tasks && typeof d.tasks === 'object') {
528
+ const metadata = d.metadata || {
529
+ project_name: 'imported-project',
530
+ version: '1.0.0',
531
+ created_at: new Date().toISOString(),
532
+ updated_at: new Date().toISOString(),
533
+ task_count: Object.keys(d.tasks || {}).length,
534
+ };
535
+ const store = new TaskGraphStore(metadata);
536
+ // Add tasks
537
+ for (const [, taskData] of Object.entries(d.tasks)) {
538
+ const node = TaskNode.fromJSON(taskData);
539
+ store.addNode(node);
540
+ }
541
+ return store;
542
+ }
543
+ // Handle task array format
544
+ if (Array.isArray(data)) {
545
+ const metadata = {
546
+ project_name: 'imported-project',
547
+ version: '1.0.0',
548
+ created_at: new Date().toISOString(),
549
+ updated_at: new Date().toISOString(),
550
+ task_count: data.length,
551
+ };
552
+ const store = new TaskGraphStore(metadata);
553
+ for (const taskData of data) {
554
+ const node = TaskNode.fromJSON(taskData);
555
+ store.addNode(node);
556
+ }
557
+ return store;
558
+ }
559
+ // Handle single task format
560
+ if (d.id && d.title) {
561
+ const metadata = {
562
+ project_name: 'imported-project',
563
+ version: '1.0.0',
564
+ created_at: new Date().toISOString(),
565
+ updated_at: new Date().toISOString(),
566
+ task_count: 1,
567
+ };
568
+ const store = new TaskGraphStore(metadata);
569
+ const node = TaskNode.fromJSON(d);
570
+ store.addNode(node);
571
+ return store;
572
+ }
573
+ throw new Error('Could not convert import data to graph format');
574
+ }
575
+ /**
576
+ * Create TaskGraphStore from parsed markdown tasks
577
+ */
578
+ function createGraphFromMarkdownTasks(tasks) {
579
+ const metadata = {
580
+ project_name: 'imported-from-markdown',
581
+ version: '1.0.0',
582
+ created_at: new Date().toISOString(),
583
+ updated_at: new Date().toISOString(),
584
+ task_count: tasks.length,
585
+ };
586
+ const store = new TaskGraphStore(metadata);
587
+ for (const taskData of tasks) {
588
+ // Create TaskNode with _skipAtomicValidation for imported tasks
589
+ const node = new TaskNode({
590
+ id: taskData.id,
591
+ title: taskData.title,
592
+ description: taskData.description,
593
+ status: taskData.status,
594
+ priority: taskData.priority,
595
+ success_criteria: taskData.success_criteria,
596
+ deliverables: taskData.deliverables,
597
+ blockers: taskData.blockers,
598
+ dependencies: taskData.dependencies,
599
+ sub_items: taskData.sub_items,
600
+ related_files: taskData.related_files,
601
+ notes: taskData.notes,
602
+ c7_verified: taskData.c7_verified,
603
+ _skipAtomicValidation: true, // Skip validation for imported tasks
604
+ });
605
+ store.addNode(node);
606
+ }
607
+ // Add edges based on blockers
608
+ for (const taskData of tasks) {
609
+ for (const blockerId of taskData.blockers) {
610
+ if (store.getNode(blockerId)) {
611
+ store.addEdge(blockerId, taskData.id);
612
+ }
613
+ }
614
+ }
615
+ return store;
616
+ }
617
+ /**
618
+ * Merge markdown tasks with existing graph
619
+ * Matches by title (case-insensitive) or ID
620
+ */
621
+ function mergeMarkdownTasks(existing, newTasks) {
622
+ // Create a map for quick lookup by title
623
+ const existingByTitle = new Map();
624
+ for (const task of existing.getAllTasks()) {
625
+ existingByTitle.set(task.title.toLowerCase(), task);
626
+ }
627
+ for (const newTask of newTasks) {
628
+ // Try to find existing task by ID first
629
+ let existingTask = existing.getNode(newTask.id);
630
+ // If not found, try by title
631
+ if (!existingTask) {
632
+ existingTask = existingByTitle.get(newTask.title.toLowerCase());
633
+ }
634
+ if (existingTask) {
635
+ // Merge: update completion states of criteria and deliverables
636
+ for (const newSc of newTask.success_criteria) {
637
+ // Find matching criterion by text (partial match)
638
+ const existingSc = existingTask.success_criteria.find(sc => sc.text.toLowerCase().includes(newSc.text.toLowerCase()) ||
639
+ newSc.text.toLowerCase().includes(sc.text.toLowerCase()));
640
+ if (existingSc && newSc.completed && !existingSc.completed) {
641
+ existingTask.completeCriterion(existingSc.id);
642
+ }
643
+ }
644
+ for (const newDel of newTask.deliverables) {
645
+ // Find matching deliverable by text (partial match)
646
+ const existingDel = existingTask.deliverables.find(d => d.text.toLowerCase().includes(newDel.text.toLowerCase()) ||
647
+ newDel.text.toLowerCase().includes(d.text.toLowerCase()));
648
+ if (existingDel && newDel.completed && !existingDel.completed) {
649
+ existingTask.completeDeliverable(existingDel.id);
650
+ }
651
+ }
652
+ // Merge notes (append)
653
+ if (newTask.notes && !existingTask.notes.includes(newTask.notes)) {
654
+ existingTask.notes = existingTask.notes
655
+ ? `${existingTask.notes}\n\n${newTask.notes}`
656
+ : newTask.notes;
657
+ }
658
+ }
659
+ else {
660
+ // New task - add it
661
+ const node = new TaskNode({
662
+ id: newTask.id,
663
+ title: newTask.title,
664
+ description: newTask.description,
665
+ status: newTask.status,
666
+ priority: newTask.priority,
667
+ success_criteria: newTask.success_criteria,
668
+ deliverables: newTask.deliverables,
669
+ blockers: newTask.blockers,
670
+ dependencies: newTask.dependencies,
671
+ sub_items: newTask.sub_items,
672
+ related_files: newTask.related_files,
673
+ notes: newTask.notes,
674
+ c7_verified: newTask.c7_verified,
675
+ _skipAtomicValidation: true,
676
+ });
677
+ existing.addNode(node);
678
+ }
679
+ }
680
+ return existing;
681
+ }
682
+ /**
683
+ * Create the import command
684
+ */
685
+ export const importCommand = new Command('import')
686
+ .description('Import tasks from file (supports JSON and Markdown)')
687
+ .argument('<file>', 'File path to import')
688
+ .option('--format <format>', 'Import format: json | md (auto-detect from extension if not specified)')
689
+ .option('--merge', 'Merge with existing tasks instead of replacing')
690
+ .addHelpText('after', `
691
+ Examples:
692
+ $ octie import tasks.json
693
+ $ octie import tasks.md --merge
694
+
695
+ File Formats:
696
+ JSON - Full project export format
697
+ MD - Markdown with task details
698
+ `)
699
+ .action(async (file, options, command) => {
700
+ try {
701
+ // Get global options
702
+ const globalOpts = command.parent?.opts() || {};
703
+ const projectPath = await getProjectPath(globalOpts.project);
704
+ const storage = new TaskStorage({ projectDir: projectPath });
705
+ // Resolve file path
706
+ const filePath = resolve(file);
707
+ // Check file exists
708
+ if (!existsSync(filePath)) {
709
+ error(`File not found: ${filePath}`);
710
+ process.exit(1);
711
+ }
712
+ // Read file
713
+ const content = readFileSync(filePath, 'utf-8');
714
+ // Auto-detect format if not specified
715
+ const format = options.format || detectFormat(filePath);
716
+ if (!format) {
717
+ error(`Cannot detect format. Please specify --format <json|md>`);
718
+ process.exit(1);
719
+ }
720
+ let store;
721
+ const projectExists = await storage.exists();
722
+ switch (format) {
723
+ case 'json': {
724
+ let data;
725
+ try {
726
+ data = JSON.parse(content);
727
+ }
728
+ catch (parseErr) {
729
+ error(`Failed to parse JSON: ${parseErr instanceof Error ? parseErr.message : 'Unknown error'}`);
730
+ process.exit(1);
731
+ }
732
+ // Validate imported data
733
+ try {
734
+ validateImportData(data);
735
+ }
736
+ catch (validationErr) {
737
+ error(`Invalid data structure: ${validationErr instanceof Error ? validationErr.message : 'Unknown error'}`);
738
+ process.exit(1);
739
+ }
740
+ // Create backup before import if project exists
741
+ if (projectExists) {
742
+ warning('Project already exists. Creating backup before import...');
743
+ }
744
+ if (options.merge && projectExists) {
745
+ // Merge mode: load existing and merge
746
+ warning('Merging with existing tasks...');
747
+ const existing = await storage.load();
748
+ // Create store from import data
749
+ store = createGraphFromImportData(data);
750
+ // Add tasks from existing that aren't in import
751
+ for (const task of existing.getAllTasks()) {
752
+ if (!store.getNode(task.id)) {
753
+ store.addNode(task);
754
+ }
755
+ }
756
+ }
757
+ else {
758
+ // Replace mode (default)
759
+ store = createGraphFromImportData(data);
760
+ }
761
+ break;
762
+ }
763
+ case 'md': {
764
+ // Parse markdown tasks
765
+ warning('Parsing markdown file...');
766
+ const mdTasks = parseMarkdownTasks(content);
767
+ if (mdTasks.length === 0) {
768
+ error('No tasks found in markdown file. Expected format: ## [x] Task Title');
769
+ process.exit(1);
770
+ }
771
+ // Create backup before import if project exists
772
+ if (projectExists) {
773
+ warning('Project already exists. Creating backup before import...');
774
+ }
775
+ if (options.merge && projectExists) {
776
+ // Merge mode for markdown
777
+ warning('Merging markdown tasks with existing tasks...');
778
+ const existing = await storage.load();
779
+ store = mergeMarkdownTasks(existing, mdTasks);
780
+ }
781
+ else {
782
+ // Replace mode (default)
783
+ store = createGraphFromMarkdownTasks(mdTasks);
784
+ }
785
+ success(`Parsed ${mdTasks.length} task(s) from markdown`);
786
+ break;
787
+ }
788
+ default:
789
+ error(`Unsupported format: ${format}. Supported formats: json, md`);
790
+ process.exit(1);
791
+ }
792
+ // Save with storage
793
+ await storage.save(store);
794
+ success(`Imported ${store.size} task(s) from ${chalk.cyan(filePath)}`);
795
+ process.exit(0);
796
+ }
797
+ catch (err) {
798
+ if (err instanceof Error) {
799
+ error(err.message);
800
+ }
801
+ else {
802
+ error('Import failed');
803
+ }
804
+ process.exit(1);
805
+ }
806
+ });
807
+ //# sourceMappingURL=import.js.map