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.
- package/README.md +523 -0
- package/dist/cli/commands/approve.d.ts +27 -0
- package/dist/cli/commands/approve.d.ts.map +1 -0
- package/dist/cli/commands/approve.js +119 -0
- package/dist/cli/commands/approve.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +15 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +521 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/create.d.ts +9 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +321 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +9 -0
- package/dist/cli/commands/delete.d.ts.map +1 -0
- package/dist/cli/commands/delete.js +143 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/export.d.ts +9 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +66 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/find.d.ts +16 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +252 -0
- package/dist/cli/commands/find.js.map +1 -0
- package/dist/cli/commands/get.d.ts +9 -0
- package/dist/cli/commands/get.d.ts.map +1 -0
- package/dist/cli/commands/get.js +74 -0
- package/dist/cli/commands/get.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +9 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +200 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/import.d.ts +9 -0
- package/dist/cli/commands/import.d.ts.map +1 -0
- package/dist/cli/commands/import.js +807 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +57 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +9 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +175 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +9 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +113 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +9 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +94 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/update.d.ts +9 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +423 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/wire.d.ts +15 -0
- package/dist/cli/commands/wire.d.ts.map +1 -0
- package/dist/cli/commands/wire.js +164 -0
- package/dist/cli/commands/wire.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +100 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output/json.d.ts +16 -0
- package/dist/cli/output/json.d.ts.map +1 -0
- package/dist/cli/output/json.js +29 -0
- package/dist/cli/output/json.js.map +1 -0
- package/dist/cli/output/markdown.d.ts +15 -0
- package/dist/cli/output/markdown.d.ts.map +1 -0
- package/dist/cli/output/markdown.js +206 -0
- package/dist/cli/output/markdown.js.map +1 -0
- package/dist/cli/output/table.d.ts +23 -0
- package/dist/cli/output/table.d.ts.map +1 -0
- package/dist/cli/output/table.js +150 -0
- package/dist/cli/output/table.js.map +1 -0
- package/dist/cli/utils/helpers.d.ts +126 -0
- package/dist/cli/utils/helpers.d.ts.map +1 -0
- package/dist/cli/utils/helpers.js +325 -0
- package/dist/cli/utils/helpers.js.map +1 -0
- package/dist/core/graph/algorithms.d.ts +11 -0
- package/dist/core/graph/algorithms.d.ts.map +1 -0
- package/dist/core/graph/algorithms.js +14 -0
- package/dist/core/graph/algorithms.js.map +1 -0
- package/dist/core/graph/cycle.d.ts +155 -0
- package/dist/core/graph/cycle.d.ts.map +1 -0
- package/dist/core/graph/cycle.js +297 -0
- package/dist/core/graph/cycle.js.map +1 -0
- package/dist/core/graph/index.d.ts +223 -0
- package/dist/core/graph/index.d.ts.map +1 -0
- package/dist/core/graph/index.js +475 -0
- package/dist/core/graph/index.js.map +1 -0
- package/dist/core/graph/operations.d.ts +240 -0
- package/dist/core/graph/operations.d.ts.map +1 -0
- package/dist/core/graph/operations.js +503 -0
- package/dist/core/graph/operations.js.map +1 -0
- package/dist/core/graph/sort.d.ts +76 -0
- package/dist/core/graph/sort.d.ts.map +1 -0
- package/dist/core/graph/sort.js +254 -0
- package/dist/core/graph/sort.js.map +1 -0
- package/dist/core/graph/traversal.d.ts +122 -0
- package/dist/core/graph/traversal.d.ts.map +1 -0
- package/dist/core/graph/traversal.js +336 -0
- package/dist/core/graph/traversal.js.map +1 -0
- package/dist/core/models/task-node.d.ts +328 -0
- package/dist/core/models/task-node.d.ts.map +1 -0
- package/dist/core/models/task-node.js +1090 -0
- package/dist/core/models/task-node.js.map +1 -0
- package/dist/core/registry/index.d.ts +102 -0
- package/dist/core/registry/index.d.ts.map +1 -0
- package/dist/core/registry/index.js +249 -0
- package/dist/core/registry/index.js.map +1 -0
- package/dist/core/registry/root-guard.d.ts +19 -0
- package/dist/core/registry/root-guard.d.ts.map +1 -0
- package/dist/core/registry/root-guard.js +28 -0
- package/dist/core/registry/root-guard.js.map +1 -0
- package/dist/core/storage/atomic-write.d.ts +181 -0
- package/dist/core/storage/atomic-write.d.ts.map +1 -0
- package/dist/core/storage/atomic-write.js +379 -0
- package/dist/core/storage/atomic-write.js.map +1 -0
- package/dist/core/storage/file-store.d.ts +148 -0
- package/dist/core/storage/file-store.d.ts.map +1 -0
- package/dist/core/storage/file-store.js +423 -0
- package/dist/core/storage/file-store.js.map +1 -0
- package/dist/core/storage/indexer.d.ts +138 -0
- package/dist/core/storage/indexer.d.ts.map +1 -0
- package/dist/core/storage/indexer.js +350 -0
- package/dist/core/storage/indexer.js.map +1 -0
- package/dist/core/utils/status-helpers.d.ts +59 -0
- package/dist/core/utils/status-helpers.d.ts.map +1 -0
- package/dist/core/utils/status-helpers.js +149 -0
- package/dist/core/utils/status-helpers.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +504 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +182 -0
- package/dist/types/index.js.map +1 -0
- package/dist/web/routes/graph.d.ts +17 -0
- package/dist/web/routes/graph.d.ts.map +1 -0
- package/dist/web/routes/graph.js +277 -0
- package/dist/web/routes/graph.js.map +1 -0
- package/dist/web/routes/projects.d.ts +14 -0
- package/dist/web/routes/projects.d.ts.map +1 -0
- package/dist/web/routes/projects.js +102 -0
- package/dist/web/routes/projects.js.map +1 -0
- package/dist/web/routes/tasks.d.ts +17 -0
- package/dist/web/routes/tasks.d.ts.map +1 -0
- package/dist/web/routes/tasks.js +538 -0
- package/dist/web/routes/tasks.js.map +1 -0
- package/dist/web/server.d.ts +121 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +389 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web-ui/assets/index-BB0qvF1y.css +1 -0
- package/dist/web-ui/assets/index-Vmm72oKY.js +34 -0
- package/dist/web-ui/index.html +14 -0
- package/dist/web-ui/vite.svg +1 -0
- 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
|