@riotprompt/riotplan 0.0.4 → 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 +214 -7
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli-DmsyaX1E.js +6189 -0
- package/dist/cli-DmsyaX1E.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +2309 -228
- package/dist/index.js +1631 -46
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/.kodrdriv-test-cache.json +0 -6
package/dist/index.js
CHANGED
|
@@ -1,58 +1,1513 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import { P as PLAN_CONVENTIONS, l as loadPlan, r as renderToHtml, a as renderToJson, b as renderToMarkdown } from "./cli-DmsyaX1E.js";
|
|
2
|
+
import { V, aa, W, ab, Y, a9, X, Z, U, a2, w, F, G, ag, af, y, J, h, k, c, _, B, I, o, L, ae, M, n, K, Q, j, a0, p, m, O, S, g, i, f, t, E, ac, ad, C, D, $, a7, R, q, a8, e, d, s, T, A, z, a3, a4, a5, a6, x, u, N, a1, H, v } from "./cli-DmsyaX1E.js";
|
|
3
|
+
import { readFile, access, writeFile, readdir, mkdir } from "node:fs/promises";
|
|
4
|
+
import { resolve, join, isAbsolute, relative, basename, dirname } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
function parseStatus(content, options = {}) {
|
|
8
|
+
const warnings = [];
|
|
9
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
10
|
+
const title = titleMatch ? titleMatch[1].trim() : "Unknown Plan";
|
|
11
|
+
const currentState = parseCurrentState(content);
|
|
12
|
+
const stepProgress = parseStepProgress(content);
|
|
13
|
+
const blockers = parseBlockers(content);
|
|
14
|
+
const issues = parseIssues(content);
|
|
15
|
+
const notes = parseNotes(content);
|
|
16
|
+
const document = {
|
|
17
|
+
title,
|
|
18
|
+
currentState,
|
|
19
|
+
stepProgress,
|
|
20
|
+
blockers,
|
|
21
|
+
issues,
|
|
22
|
+
notes
|
|
23
|
+
};
|
|
24
|
+
const completedSteps = stepProgress.filter(
|
|
25
|
+
(s2) => s2.status === "completed"
|
|
26
|
+
).length;
|
|
27
|
+
const progress = stepProgress.length > 0 ? Math.round(completedSteps / stepProgress.length * 100) : 0;
|
|
28
|
+
const state = {
|
|
29
|
+
status: currentState.status,
|
|
30
|
+
lastUpdatedAt: currentState.lastUpdated ? new Date(currentState.lastUpdated) : /* @__PURE__ */ new Date(),
|
|
31
|
+
blockers: blockers.map(
|
|
32
|
+
(desc, i2) => ({
|
|
33
|
+
id: `blocker-${i2 + 1}`,
|
|
34
|
+
description: desc,
|
|
35
|
+
severity: "medium",
|
|
36
|
+
affectedSteps: [],
|
|
37
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
38
|
+
})
|
|
39
|
+
),
|
|
40
|
+
issues: issues.map(
|
|
41
|
+
(desc, i2) => ({
|
|
42
|
+
id: `issue-${i2 + 1}`,
|
|
43
|
+
title: desc.substring(0, 50),
|
|
44
|
+
description: desc,
|
|
45
|
+
severity: "medium",
|
|
46
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
47
|
+
})
|
|
48
|
+
),
|
|
49
|
+
progress
|
|
50
|
+
};
|
|
51
|
+
if (currentState.startedAt) {
|
|
52
|
+
state.startedAt = new Date(currentState.startedAt);
|
|
53
|
+
}
|
|
54
|
+
if (options.steps) {
|
|
55
|
+
const currentStepNum = extractStepNumber(currentState.currentStep);
|
|
56
|
+
if (currentStepNum) state.currentStep = currentStepNum;
|
|
57
|
+
const lastStepNum = extractStepNumber(currentState.lastCompleted);
|
|
58
|
+
if (lastStepNum) state.lastCompletedStep = lastStepNum;
|
|
59
|
+
if (stepProgress.length > 0 && stepProgress.length !== options.steps.length) {
|
|
60
|
+
warnings.push(
|
|
61
|
+
`Step count mismatch: STATUS.md has ${stepProgress.length}, plan has ${options.steps.length}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { document, state, warnings };
|
|
66
|
+
}
|
|
67
|
+
function parseCurrentState(content) {
|
|
68
|
+
const state = {
|
|
69
|
+
status: "pending"
|
|
70
|
+
};
|
|
71
|
+
const statusMatch = content.match(
|
|
72
|
+
/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)/i
|
|
73
|
+
);
|
|
74
|
+
if (statusMatch) {
|
|
75
|
+
const statusText = statusMatch[1].trim();
|
|
76
|
+
const emoji = findStatusEmoji(statusText);
|
|
77
|
+
if (emoji) {
|
|
78
|
+
state.status = parseStatusEmoji(emoji);
|
|
79
|
+
} else {
|
|
80
|
+
state.status = parseStatusText(statusText);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const currentMatch = content.match(
|
|
84
|
+
/\|\s*\*\*Current Step\*\*\s*\|\s*([^|]+)/i
|
|
85
|
+
);
|
|
86
|
+
if (currentMatch && currentMatch[1].trim() !== "-") {
|
|
87
|
+
state.currentStep = currentMatch[1].trim();
|
|
88
|
+
}
|
|
89
|
+
const lastMatch = content.match(
|
|
90
|
+
/\|\s*\*\*Last Completed\*\*\s*\|\s*([^|]+)/i
|
|
91
|
+
);
|
|
92
|
+
if (lastMatch && lastMatch[1].trim() !== "-") {
|
|
93
|
+
state.lastCompleted = lastMatch[1].trim();
|
|
94
|
+
}
|
|
95
|
+
const startedMatch = content.match(
|
|
96
|
+
/\|\s*\*\*Started\*\*\s*\|\s*([^|]+)/i
|
|
97
|
+
);
|
|
98
|
+
if (startedMatch && startedMatch[1].trim() !== "-") {
|
|
99
|
+
state.startedAt = startedMatch[1].trim();
|
|
100
|
+
}
|
|
101
|
+
const updatedMatch = content.match(
|
|
102
|
+
/\|\s*\*\*Last Updated\*\*\s*\|\s*([^|]+)/i
|
|
103
|
+
);
|
|
104
|
+
if (updatedMatch && updatedMatch[1].trim() !== "-") {
|
|
105
|
+
state.lastUpdated = updatedMatch[1].trim();
|
|
106
|
+
}
|
|
107
|
+
return state;
|
|
108
|
+
}
|
|
109
|
+
const STATUS_EMOJIS = ["⬜", "🔄", "✅", "❌", "⏸️", "⏭️"];
|
|
110
|
+
function findStatusEmoji(text) {
|
|
111
|
+
for (const emoji of STATUS_EMOJIS) {
|
|
112
|
+
if (text.includes(emoji)) {
|
|
113
|
+
return emoji;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function parseStatusEmoji(emoji) {
|
|
119
|
+
return PLAN_CONVENTIONS.emojiToStatus[emoji] || "pending";
|
|
120
|
+
}
|
|
121
|
+
function parseStatusText(text) {
|
|
122
|
+
const normalized = text.toLowerCase().replace(/[^a-z_]/g, "");
|
|
123
|
+
const valid = [
|
|
124
|
+
"pending",
|
|
125
|
+
"in_progress",
|
|
126
|
+
"completed",
|
|
127
|
+
"failed",
|
|
128
|
+
"blocked",
|
|
129
|
+
"skipped"
|
|
130
|
+
];
|
|
131
|
+
if (valid.includes(normalized)) {
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
if (normalized.includes("progress") || normalized.includes("inprogress")) {
|
|
135
|
+
return "in_progress";
|
|
136
|
+
}
|
|
137
|
+
if (normalized.includes("complete") || normalized.includes("done")) {
|
|
138
|
+
return "completed";
|
|
139
|
+
}
|
|
140
|
+
if (normalized.includes("block")) {
|
|
141
|
+
return "blocked";
|
|
142
|
+
}
|
|
143
|
+
if (normalized.includes("skip")) {
|
|
144
|
+
return "skipped";
|
|
145
|
+
}
|
|
146
|
+
if (normalized.includes("fail") || normalized.includes("error")) {
|
|
147
|
+
return "failed";
|
|
148
|
+
}
|
|
149
|
+
return "pending";
|
|
150
|
+
}
|
|
151
|
+
function parseStepProgress(content) {
|
|
152
|
+
const steps = [];
|
|
153
|
+
const tableMatch = content.match(
|
|
154
|
+
/\|\s*Step\s*\|\s*Name\s*\|[^\n]*\n\|[-\s|]+\n([\s\S]*?)(?=\n\n|\n##|$)/i
|
|
155
|
+
);
|
|
156
|
+
if (!tableMatch) return steps;
|
|
157
|
+
const tableRows = tableMatch[1].split("\n").filter((row) => row.trim());
|
|
158
|
+
for (const row of tableRows) {
|
|
159
|
+
if (row.match(/^\|[-\s|]+\|$/)) continue;
|
|
160
|
+
const cells = row.split("|").map((c2) => c2.trim()).filter((c2) => c2);
|
|
161
|
+
if (cells.length >= 3) {
|
|
162
|
+
const step = cells[0];
|
|
163
|
+
const name = cells[1];
|
|
164
|
+
const statusStr = cells[2];
|
|
165
|
+
const started = cells[3] || void 0;
|
|
166
|
+
const completed = cells[4] || void 0;
|
|
167
|
+
const notes = cells[5] || void 0;
|
|
168
|
+
const emoji = findStatusEmoji(statusStr);
|
|
169
|
+
const status = emoji ? parseStatusEmoji(emoji) : parseStatusText(statusStr);
|
|
170
|
+
steps.push({
|
|
171
|
+
step,
|
|
172
|
+
name,
|
|
173
|
+
status,
|
|
174
|
+
started: started && started !== "-" ? started : void 0,
|
|
175
|
+
completed: completed && completed !== "-" ? completed : void 0,
|
|
176
|
+
notes: notes && notes !== "-" ? notes : void 0
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return steps;
|
|
181
|
+
}
|
|
182
|
+
function parseBlockers(content) {
|
|
183
|
+
const blockers = [];
|
|
184
|
+
const section = extractSection(content, "Blockers");
|
|
185
|
+
if (!section) return blockers;
|
|
186
|
+
if (section.toLowerCase().includes("none")) {
|
|
187
|
+
return blockers;
|
|
188
|
+
}
|
|
189
|
+
const listItems = section.match(/^[-*]\s+(.+)$/gm);
|
|
190
|
+
if (listItems) {
|
|
191
|
+
for (const item of listItems) {
|
|
192
|
+
const text = item.replace(/^[-*]\s+/, "").trim();
|
|
193
|
+
if (text && !text.toLowerCase().includes("none")) {
|
|
194
|
+
blockers.push(text);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return blockers;
|
|
199
|
+
}
|
|
200
|
+
function parseIssues(content) {
|
|
201
|
+
const issues = [];
|
|
202
|
+
const section = extractSection(content, "Issues");
|
|
203
|
+
if (!section) return issues;
|
|
204
|
+
if (section.toLowerCase().includes("none")) {
|
|
205
|
+
return issues;
|
|
206
|
+
}
|
|
207
|
+
const listItems = section.match(/^[-*]\s+(.+)$/gm);
|
|
208
|
+
if (listItems) {
|
|
209
|
+
for (const item of listItems) {
|
|
210
|
+
const text = item.replace(/^[-*]\s+/, "").trim();
|
|
211
|
+
if (text && !text.toLowerCase().includes("none")) {
|
|
212
|
+
issues.push(text);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return issues;
|
|
217
|
+
}
|
|
218
|
+
function parseNotes(content) {
|
|
219
|
+
const section = extractSection(content, "Notes");
|
|
220
|
+
if (!section || section.trim() === "") return void 0;
|
|
221
|
+
const trimmed = section.trim();
|
|
222
|
+
if (trimmed === "-" || trimmed.toLowerCase() === "none" || trimmed.toLowerCase() === "none currently.") {
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
return trimmed;
|
|
226
|
+
}
|
|
227
|
+
function extractSection(content, name) {
|
|
228
|
+
const regex = new RegExp(
|
|
229
|
+
`##\\s*${name}[^\\n]*\\n([\\s\\S]*?)(?=\\n##|\\n---|$)`,
|
|
230
|
+
"i"
|
|
231
|
+
);
|
|
232
|
+
const match = content.match(regex);
|
|
233
|
+
return match ? match[1].trim() : void 0;
|
|
234
|
+
}
|
|
235
|
+
function extractStepNumber(stepRef) {
|
|
236
|
+
if (!stepRef) return void 0;
|
|
237
|
+
const match = stepRef.match(/(\d+)/);
|
|
238
|
+
return match ? parseInt(match[1]) : void 0;
|
|
239
|
+
}
|
|
240
|
+
function parseRelationshipsFromContent(content) {
|
|
241
|
+
const relationships = [];
|
|
242
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
243
|
+
if (frontmatterMatch) {
|
|
244
|
+
const frontmatter = frontmatterMatch[1];
|
|
245
|
+
const spawnedFrom = frontmatter.match(/spawned-from:\s*(.+)/);
|
|
246
|
+
if (spawnedFrom) {
|
|
247
|
+
relationships.push({
|
|
248
|
+
type: "spawned-from",
|
|
249
|
+
targetPath: spawnedFrom[1].trim()
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const blocksMatch = frontmatter.match(/blocks:\s*(.+)/);
|
|
253
|
+
if (blocksMatch) {
|
|
254
|
+
const paths = blocksMatch[1].split(",").map((p2) => p2.trim());
|
|
255
|
+
for (const path of paths) {
|
|
256
|
+
relationships.push({
|
|
257
|
+
type: "blocks",
|
|
258
|
+
targetPath: path
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const blockedBy = frontmatter.match(/blocked-by:\s*(.+)/);
|
|
263
|
+
if (blockedBy) {
|
|
264
|
+
const paths = blockedBy[1].split(",").map((p2) => p2.trim());
|
|
265
|
+
for (const path of paths) {
|
|
266
|
+
relationships.push({
|
|
267
|
+
type: "blocked-by",
|
|
268
|
+
targetPath: path
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const relatedMatch = frontmatter.match(/related:\s*(.+)/);
|
|
273
|
+
if (relatedMatch) {
|
|
274
|
+
const paths = relatedMatch[1].split(",").map((p2) => p2.trim());
|
|
275
|
+
for (const path of paths) {
|
|
276
|
+
relationships.push({
|
|
277
|
+
type: "related",
|
|
278
|
+
targetPath: path
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const sectionMatch = content.match(
|
|
284
|
+
/##\s+Related\s+Plans?\s*\n([\s\S]*?)(?=\n##|\n#\s|$)/i
|
|
285
|
+
);
|
|
286
|
+
if (sectionMatch) {
|
|
287
|
+
const section = sectionMatch[1];
|
|
288
|
+
const lines = section.split("\n");
|
|
289
|
+
for (const line of lines) {
|
|
290
|
+
const trimmed = line.trim();
|
|
291
|
+
if (!trimmed.startsWith("-")) continue;
|
|
292
|
+
const itemContent = trimmed.slice(1).trim();
|
|
293
|
+
const typePathMatch = itemContent.match(
|
|
294
|
+
/\*\*(\w+(?:-\w+)?)\*\*:\s*([^\s]+)(?:\s+-\s+(.+))?/
|
|
295
|
+
);
|
|
296
|
+
if (typePathMatch) {
|
|
297
|
+
const [, typeStr, path, reason] = typePathMatch;
|
|
298
|
+
const type = normalizeRelationType(typeStr);
|
|
299
|
+
if (type) {
|
|
300
|
+
relationships.push({
|
|
301
|
+
type,
|
|
302
|
+
targetPath: path,
|
|
303
|
+
reason: reason?.trim()
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const linkMatch = itemContent.match(
|
|
309
|
+
/\[([^\]]+)\]\(([^)]+)\)(?:\s*-\s*(.+))?/
|
|
310
|
+
);
|
|
311
|
+
if (linkMatch) {
|
|
312
|
+
const [, , path, desc] = linkMatch;
|
|
313
|
+
let type = "related";
|
|
314
|
+
let reason = desc?.trim();
|
|
315
|
+
if (desc) {
|
|
316
|
+
const typeInDesc = desc.match(
|
|
317
|
+
/\((spawned-from|spawned|blocks|blocked-by|related)\)/i
|
|
318
|
+
);
|
|
319
|
+
if (typeInDesc) {
|
|
320
|
+
type = normalizeRelationType(typeInDesc[1]) || "related";
|
|
321
|
+
reason = desc.replace(typeInDesc[0], "").trim();
|
|
322
|
+
} else if (desc.toLowerCase().includes("blocks")) {
|
|
323
|
+
type = "blocks";
|
|
324
|
+
} else if (desc.toLowerCase().includes("blocked")) {
|
|
325
|
+
type = "blocked-by";
|
|
326
|
+
} else if (desc.toLowerCase().includes("spawned from")) {
|
|
327
|
+
type = "spawned-from";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
relationships.push({
|
|
331
|
+
type,
|
|
332
|
+
targetPath: path,
|
|
333
|
+
reason: reason || void 0
|
|
334
|
+
});
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const simpleMatch = itemContent.match(
|
|
338
|
+
/([^\s(]+)\s*(?:\((\w+(?:-\w+)?)\))?(?:\s*-\s*(.+))?/
|
|
339
|
+
);
|
|
340
|
+
if (simpleMatch) {
|
|
341
|
+
const [, path, typeStr, reason] = simpleMatch;
|
|
342
|
+
const type = typeStr ? normalizeRelationType(typeStr) || "related" : "related";
|
|
343
|
+
relationships.push({
|
|
344
|
+
type,
|
|
345
|
+
targetPath: path,
|
|
346
|
+
reason: reason?.trim()
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
352
|
+
return relationships.filter((r) => {
|
|
353
|
+
const key = `${r.type}:${r.targetPath}`;
|
|
354
|
+
if (seen.has(key)) return false;
|
|
355
|
+
seen.add(key);
|
|
356
|
+
return true;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function normalizeRelationType(str) {
|
|
360
|
+
const normalized = str.toLowerCase().replace(/_/g, "-");
|
|
361
|
+
const validTypes = [
|
|
362
|
+
"spawned-from",
|
|
363
|
+
"spawned",
|
|
364
|
+
"blocks",
|
|
365
|
+
"blocked-by",
|
|
366
|
+
"related"
|
|
367
|
+
];
|
|
368
|
+
return validTypes.find((t2) => t2 === normalized) || null;
|
|
369
|
+
}
|
|
370
|
+
async function parseRelationshipsFromPlan(planPath) {
|
|
371
|
+
const absolutePath = resolve(planPath);
|
|
372
|
+
const relationships = [];
|
|
373
|
+
try {
|
|
374
|
+
const summaryPath = join(absolutePath, "SUMMARY.md");
|
|
375
|
+
const content = await readFile(summaryPath, "utf-8");
|
|
376
|
+
relationships.push(...parseRelationshipsFromContent(content));
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const files = ["prompt-of-prompts.md"];
|
|
381
|
+
const planCode = absolutePath.split("/").pop();
|
|
382
|
+
if (planCode) {
|
|
383
|
+
files.push(`${planCode}-prompt.md`, `${planCode}.md`);
|
|
384
|
+
}
|
|
385
|
+
for (const file of files) {
|
|
386
|
+
try {
|
|
387
|
+
const filePath = join(absolutePath, file);
|
|
388
|
+
const content = await readFile(filePath, "utf-8");
|
|
389
|
+
relationships.push(...parseRelationshipsFromContent(content));
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
396
|
+
return relationships.filter((r) => {
|
|
397
|
+
const key = `${r.type}:${r.targetPath}`;
|
|
398
|
+
if (seen.has(key)) return false;
|
|
399
|
+
seen.add(key);
|
|
400
|
+
return true;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async function addRelationship(plan, options) {
|
|
404
|
+
const { type, targetPath, steps, reason } = options;
|
|
405
|
+
const resolvedTarget = isAbsolute(targetPath) ? targetPath : resolve(plan.metadata.path, targetPath);
|
|
406
|
+
let targetValid = false;
|
|
407
|
+
let targetPlan;
|
|
408
|
+
try {
|
|
409
|
+
await access(resolvedTarget);
|
|
410
|
+
const target = await loadPlan(resolvedTarget);
|
|
411
|
+
targetValid = true;
|
|
412
|
+
targetPlan = {
|
|
413
|
+
code: target.metadata.code,
|
|
414
|
+
name: target.metadata.name,
|
|
415
|
+
path: resolvedTarget
|
|
416
|
+
};
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
const relationship = {
|
|
420
|
+
type,
|
|
421
|
+
planPath: targetPath,
|
|
422
|
+
// Store original path
|
|
423
|
+
steps,
|
|
424
|
+
reason,
|
|
425
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
426
|
+
};
|
|
427
|
+
if (!plan.relationships) {
|
|
428
|
+
plan.relationships = [];
|
|
429
|
+
}
|
|
430
|
+
plan.relationships.push(relationship);
|
|
431
|
+
return {
|
|
432
|
+
relationship,
|
|
433
|
+
targetValid,
|
|
434
|
+
targetPlan
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function removeRelationship(plan, targetPath, type) {
|
|
438
|
+
if (!plan.relationships) return [];
|
|
439
|
+
const removed = [];
|
|
440
|
+
plan.relationships = plan.relationships.filter((r) => {
|
|
441
|
+
const matches = r.planPath === targetPath && (type === void 0 || r.type === type);
|
|
442
|
+
if (matches) removed.push(r);
|
|
443
|
+
return !matches;
|
|
444
|
+
});
|
|
445
|
+
return removed;
|
|
446
|
+
}
|
|
447
|
+
function getRelationshipsByType(plan, type) {
|
|
448
|
+
return (plan.relationships || []).filter((r) => r.type === type);
|
|
449
|
+
}
|
|
450
|
+
function getInverseRelationType(type) {
|
|
451
|
+
switch (type) {
|
|
452
|
+
case "spawned-from":
|
|
453
|
+
return "spawned";
|
|
454
|
+
case "spawned":
|
|
455
|
+
return "spawned-from";
|
|
456
|
+
case "blocks":
|
|
457
|
+
return "blocked-by";
|
|
458
|
+
case "blocked-by":
|
|
459
|
+
return "blocks";
|
|
460
|
+
case "related":
|
|
461
|
+
return "related";
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function createBidirectionalRelationship(sourcePlan, targetPlan, type, reason) {
|
|
465
|
+
if (!sourcePlan.relationships) {
|
|
466
|
+
sourcePlan.relationships = [];
|
|
467
|
+
}
|
|
468
|
+
sourcePlan.relationships.push({
|
|
469
|
+
type,
|
|
470
|
+
planPath: relative(sourcePlan.metadata.path, targetPlan.metadata.path),
|
|
471
|
+
reason,
|
|
472
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
473
|
+
});
|
|
474
|
+
if (!targetPlan.relationships) {
|
|
475
|
+
targetPlan.relationships = [];
|
|
476
|
+
}
|
|
477
|
+
targetPlan.relationships.push({
|
|
478
|
+
type: getInverseRelationType(type),
|
|
479
|
+
planPath: relative(targetPlan.metadata.path, sourcePlan.metadata.path),
|
|
480
|
+
reason,
|
|
481
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
async function validateRelationships(plan) {
|
|
485
|
+
const invalid = [];
|
|
486
|
+
const validRelationships = [];
|
|
487
|
+
if (!plan.relationships) {
|
|
488
|
+
return { valid: true, invalid: [], validRelationships: [] };
|
|
489
|
+
}
|
|
490
|
+
for (const rel of plan.relationships) {
|
|
491
|
+
const resolvedPath = isAbsolute(rel.planPath) ? rel.planPath : resolve(plan.metadata.path, rel.planPath);
|
|
492
|
+
try {
|
|
493
|
+
await access(resolvedPath);
|
|
494
|
+
await loadPlan(resolvedPath);
|
|
495
|
+
validRelationships.push(rel);
|
|
496
|
+
} catch {
|
|
497
|
+
invalid.push({
|
|
498
|
+
relationship: rel,
|
|
499
|
+
reason: `Target plan not found: ${rel.planPath}`
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
valid: invalid.length === 0,
|
|
505
|
+
invalid,
|
|
506
|
+
validRelationships
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function getBlockingPlans(plan) {
|
|
510
|
+
return getRelationshipsByType(plan, "blocked-by").map((r) => r.planPath);
|
|
511
|
+
}
|
|
512
|
+
function getBlockedPlans(plan) {
|
|
513
|
+
return getRelationshipsByType(plan, "blocks").map((r) => r.planPath);
|
|
514
|
+
}
|
|
515
|
+
function getParentPlan(plan) {
|
|
516
|
+
const spawnedFrom = getRelationshipsByType(plan, "spawned-from");
|
|
517
|
+
return spawnedFrom.length > 0 ? spawnedFrom[0].planPath : null;
|
|
518
|
+
}
|
|
519
|
+
function getChildPlans(plan) {
|
|
520
|
+
return getRelationshipsByType(plan, "spawned").map((r) => r.planPath);
|
|
521
|
+
}
|
|
522
|
+
function getRelatedPlans(plan) {
|
|
523
|
+
return getRelationshipsByType(plan, "related").map((r) => r.planPath);
|
|
524
|
+
}
|
|
525
|
+
function generateRelationshipsMarkdown(plan) {
|
|
526
|
+
if (!plan.relationships || plan.relationships.length === 0) {
|
|
527
|
+
return "";
|
|
528
|
+
}
|
|
529
|
+
const lines = ["## Related Plans", ""];
|
|
530
|
+
const byType = /* @__PURE__ */ new Map();
|
|
531
|
+
for (const rel of plan.relationships) {
|
|
532
|
+
const list = byType.get(rel.type) || [];
|
|
533
|
+
list.push(rel);
|
|
534
|
+
byType.set(rel.type, list);
|
|
535
|
+
}
|
|
536
|
+
const typeLabels = {
|
|
537
|
+
"spawned-from": "Spawned From",
|
|
538
|
+
spawned: "Spawned",
|
|
539
|
+
blocks: "Blocks",
|
|
540
|
+
"blocked-by": "Blocked By",
|
|
541
|
+
related: "Related"
|
|
542
|
+
};
|
|
543
|
+
for (const [type, rels] of byType) {
|
|
544
|
+
lines.push(`### ${typeLabels[type]}`);
|
|
545
|
+
lines.push("");
|
|
546
|
+
for (const rel of rels) {
|
|
547
|
+
let line = `- **${type}**: ${rel.planPath}`;
|
|
548
|
+
if (rel.reason) {
|
|
549
|
+
line += ` - ${rel.reason}`;
|
|
550
|
+
}
|
|
551
|
+
if (rel.steps && rel.steps.length > 0) {
|
|
552
|
+
line += ` (Steps: ${rel.steps.join(", ")})`;
|
|
553
|
+
}
|
|
554
|
+
lines.push(line);
|
|
555
|
+
}
|
|
556
|
+
lines.push("");
|
|
557
|
+
}
|
|
558
|
+
return lines.join("\n");
|
|
559
|
+
}
|
|
560
|
+
async function updatePlanRelationships(plan) {
|
|
561
|
+
const summaryPath = join(plan.metadata.path, "SUMMARY.md");
|
|
562
|
+
let content;
|
|
563
|
+
try {
|
|
564
|
+
content = await readFile(summaryPath, "utf-8");
|
|
565
|
+
} catch {
|
|
566
|
+
content = `# ${plan.metadata.name}
|
|
567
|
+
|
|
568
|
+
${plan.metadata.description || "Plan summary."}
|
|
569
|
+
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
content = content.replace(/\n##\s+Related\s+Plans?\s*\n[\s\S]*?(?=\n##|\n#\s|$)/i, "");
|
|
573
|
+
const relSection = generateRelationshipsMarkdown(plan);
|
|
574
|
+
if (relSection) {
|
|
575
|
+
content = content.trimEnd() + "\n\n" + relSection;
|
|
576
|
+
}
|
|
577
|
+
await writeFile(summaryPath, content);
|
|
578
|
+
}
|
|
579
|
+
const REGISTRY_FILENAME = ".riotplan-registry.json";
|
|
580
|
+
const REGISTRY_VERSION = "1.0";
|
|
581
|
+
function getDefaultRegistryPath() {
|
|
582
|
+
return join(homedir(), REGISTRY_FILENAME);
|
|
583
|
+
}
|
|
584
|
+
function createRegistry(searchPaths = []) {
|
|
585
|
+
return {
|
|
586
|
+
version: REGISTRY_VERSION,
|
|
587
|
+
lastUpdatedAt: /* @__PURE__ */ new Date(),
|
|
588
|
+
searchPaths: searchPaths.map((p2) => resolve(p2)),
|
|
589
|
+
plans: /* @__PURE__ */ new Map(),
|
|
590
|
+
byCode: /* @__PURE__ */ new Map(),
|
|
591
|
+
byStatus: /* @__PURE__ */ new Map()
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async function loadRegistry(path) {
|
|
595
|
+
const registryPath = path || getDefaultRegistryPath();
|
|
596
|
+
try {
|
|
597
|
+
await access(registryPath);
|
|
598
|
+
const content = await readFile(registryPath, "utf-8");
|
|
599
|
+
const data = JSON.parse(content);
|
|
600
|
+
const registry = createRegistry(data.searchPaths || []);
|
|
601
|
+
registry.version = data.version || REGISTRY_VERSION;
|
|
602
|
+
registry.lastUpdatedAt = new Date(data.lastUpdatedAt);
|
|
603
|
+
if (data.plans && Array.isArray(data.plans)) {
|
|
604
|
+
for (const plan of data.plans) {
|
|
605
|
+
const entry = {
|
|
606
|
+
...plan,
|
|
607
|
+
registeredAt: new Date(plan.registeredAt),
|
|
608
|
+
lastScannedAt: new Date(plan.lastScannedAt)
|
|
609
|
+
};
|
|
610
|
+
registry.plans.set(entry.path, entry);
|
|
611
|
+
indexPlan(registry, entry);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return registry;
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function saveRegistry(registry, path) {
|
|
620
|
+
const registryPath = path || getDefaultRegistryPath();
|
|
621
|
+
registry.lastUpdatedAt = /* @__PURE__ */ new Date();
|
|
622
|
+
const data = {
|
|
623
|
+
version: registry.version,
|
|
624
|
+
lastUpdatedAt: registry.lastUpdatedAt.toISOString(),
|
|
625
|
+
searchPaths: registry.searchPaths,
|
|
626
|
+
plans: Array.from(registry.plans.values()).map((plan) => ({
|
|
627
|
+
...plan,
|
|
628
|
+
registeredAt: plan.registeredAt.toISOString(),
|
|
629
|
+
lastScannedAt: plan.lastScannedAt.toISOString()
|
|
630
|
+
}))
|
|
631
|
+
};
|
|
632
|
+
await writeFile(registryPath, JSON.stringify(data, null, 2));
|
|
633
|
+
}
|
|
634
|
+
async function scanForPlans(registry, options = {}) {
|
|
635
|
+
const {
|
|
636
|
+
searchPaths = registry.searchPaths,
|
|
637
|
+
maxDepth = 3,
|
|
638
|
+
includeHidden = false,
|
|
639
|
+
excludeDirs = ["node_modules", ".git", "dist", "coverage"]
|
|
640
|
+
} = options;
|
|
641
|
+
let discovered = 0;
|
|
642
|
+
for (const searchPath of searchPaths) {
|
|
643
|
+
const absolutePath = resolve(searchPath);
|
|
644
|
+
try {
|
|
645
|
+
discovered += await scanDirectory(
|
|
646
|
+
registry,
|
|
647
|
+
absolutePath,
|
|
648
|
+
0,
|
|
649
|
+
maxDepth,
|
|
650
|
+
includeHidden,
|
|
651
|
+
excludeDirs
|
|
652
|
+
);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const path of searchPaths) {
|
|
657
|
+
const absolutePath = resolve(path);
|
|
658
|
+
if (!registry.searchPaths.includes(absolutePath)) {
|
|
659
|
+
registry.searchPaths.push(absolutePath);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
registry.lastUpdatedAt = /* @__PURE__ */ new Date();
|
|
663
|
+
return discovered;
|
|
664
|
+
}
|
|
665
|
+
async function scanDirectory(registry, dirPath, depth, maxDepth, includeHidden, excludeDirs) {
|
|
666
|
+
if (depth > maxDepth) return 0;
|
|
667
|
+
let discovered = 0;
|
|
668
|
+
const dirName = basename(dirPath);
|
|
669
|
+
if (excludeDirs.includes(dirName)) return 0;
|
|
670
|
+
if (!includeHidden && dirName.startsWith(".")) return 0;
|
|
671
|
+
if (await isPlanDirectory(dirPath)) {
|
|
672
|
+
try {
|
|
673
|
+
const entry = await createPlanEntry(dirPath);
|
|
674
|
+
registerPlan(registry, entry);
|
|
675
|
+
discovered++;
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
return discovered;
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
682
|
+
for (const entry of entries) {
|
|
683
|
+
if (entry.isDirectory()) {
|
|
684
|
+
const subPath = join(dirPath, entry.name);
|
|
685
|
+
discovered += await scanDirectory(
|
|
686
|
+
registry,
|
|
687
|
+
subPath,
|
|
688
|
+
depth + 1,
|
|
689
|
+
maxDepth,
|
|
690
|
+
includeHidden,
|
|
691
|
+
excludeDirs
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
return discovered;
|
|
698
|
+
}
|
|
699
|
+
async function isPlanDirectory(dirPath) {
|
|
700
|
+
const indicators = [
|
|
701
|
+
"SUMMARY.md",
|
|
702
|
+
"STATUS.md",
|
|
703
|
+
"EXECUTION_PLAN.md",
|
|
704
|
+
"plan/"
|
|
705
|
+
// plan subdirectory
|
|
706
|
+
];
|
|
707
|
+
let matches = 0;
|
|
708
|
+
for (const indicator of indicators) {
|
|
709
|
+
try {
|
|
710
|
+
const checkPath = join(dirPath, indicator);
|
|
711
|
+
await access(checkPath);
|
|
712
|
+
matches++;
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (matches >= 2) return true;
|
|
717
|
+
try {
|
|
718
|
+
const files = await readdir(dirPath);
|
|
719
|
+
const hasSteps = files.some((f2) => PLAN_CONVENTIONS.stepPattern.test(f2));
|
|
720
|
+
if (hasSteps) return true;
|
|
721
|
+
} catch {
|
|
722
|
+
}
|
|
723
|
+
try {
|
|
724
|
+
const planDir = join(dirPath, "plan");
|
|
725
|
+
const files = await readdir(planDir);
|
|
726
|
+
const hasSteps = files.some((f2) => PLAN_CONVENTIONS.stepPattern.test(f2));
|
|
727
|
+
if (hasSteps) return true;
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
async function createPlanEntry(planPath) {
|
|
733
|
+
const plan = await loadPlan(planPath);
|
|
734
|
+
const now = /* @__PURE__ */ new Date();
|
|
735
|
+
return {
|
|
736
|
+
code: plan.metadata.code,
|
|
737
|
+
name: plan.metadata.name,
|
|
738
|
+
path: planPath,
|
|
739
|
+
status: plan.state.status,
|
|
740
|
+
progress: plan.state.progress,
|
|
741
|
+
stepCount: plan.steps.length,
|
|
742
|
+
completedSteps: plan.steps.filter((s2) => s2.status === "completed").length,
|
|
743
|
+
registeredAt: now,
|
|
744
|
+
lastScannedAt: now,
|
|
745
|
+
tags: plan.metadata.tags,
|
|
746
|
+
description: plan.metadata.description?.slice(0, 200),
|
|
747
|
+
parentPlan: plan.relationships?.find((r) => r.type === "spawned-from")?.planPath
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function registerPlan(registry, entry) {
|
|
751
|
+
const existing = registry.plans.get(entry.path);
|
|
752
|
+
if (existing) {
|
|
753
|
+
removeFromIndex(registry, existing);
|
|
754
|
+
}
|
|
755
|
+
registry.plans.set(entry.path, entry);
|
|
756
|
+
indexPlan(registry, entry);
|
|
757
|
+
}
|
|
758
|
+
function unregisterPlan(registry, path) {
|
|
759
|
+
const entry = registry.plans.get(path);
|
|
760
|
+
if (!entry) return false;
|
|
761
|
+
removeFromIndex(registry, entry);
|
|
762
|
+
registry.plans.delete(path);
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
function indexPlan(registry, entry) {
|
|
766
|
+
const codePaths = registry.byCode.get(entry.code) || [];
|
|
767
|
+
if (!codePaths.includes(entry.path)) {
|
|
768
|
+
codePaths.push(entry.path);
|
|
769
|
+
registry.byCode.set(entry.code, codePaths);
|
|
770
|
+
}
|
|
771
|
+
const statusPaths = registry.byStatus.get(entry.status) || [];
|
|
772
|
+
if (!statusPaths.includes(entry.path)) {
|
|
773
|
+
statusPaths.push(entry.path);
|
|
774
|
+
registry.byStatus.set(entry.status, statusPaths);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function removeFromIndex(registry, entry) {
|
|
778
|
+
const codePaths = registry.byCode.get(entry.code);
|
|
779
|
+
if (codePaths) {
|
|
780
|
+
const idx = codePaths.indexOf(entry.path);
|
|
781
|
+
if (idx !== -1) {
|
|
782
|
+
codePaths.splice(idx, 1);
|
|
783
|
+
if (codePaths.length === 0) {
|
|
784
|
+
registry.byCode.delete(entry.code);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const statusPaths = registry.byStatus.get(entry.status);
|
|
789
|
+
if (statusPaths) {
|
|
790
|
+
const idx = statusPaths.indexOf(entry.path);
|
|
791
|
+
if (idx !== -1) {
|
|
792
|
+
statusPaths.splice(idx, 1);
|
|
793
|
+
if (statusPaths.length === 0) {
|
|
794
|
+
registry.byStatus.delete(entry.status);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async function refreshPlan(registry, path) {
|
|
800
|
+
const existing = registry.plans.get(path);
|
|
801
|
+
try {
|
|
802
|
+
const entry = await createPlanEntry(path);
|
|
803
|
+
if (existing) {
|
|
804
|
+
entry.registeredAt = existing.registeredAt;
|
|
805
|
+
}
|
|
806
|
+
registerPlan(registry, entry);
|
|
807
|
+
return entry;
|
|
808
|
+
} catch {
|
|
809
|
+
if (existing) {
|
|
810
|
+
unregisterPlan(registry, path);
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
async function refreshAllPlans(registry) {
|
|
816
|
+
let updated = 0;
|
|
817
|
+
let removed = 0;
|
|
818
|
+
const paths = Array.from(registry.plans.keys());
|
|
819
|
+
for (const path of paths) {
|
|
820
|
+
const result = await refreshPlan(registry, path);
|
|
821
|
+
if (result) {
|
|
822
|
+
updated++;
|
|
823
|
+
} else {
|
|
824
|
+
removed++;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
registry.lastUpdatedAt = /* @__PURE__ */ new Date();
|
|
828
|
+
return { updated, removed };
|
|
829
|
+
}
|
|
830
|
+
function searchPlans(registry, options = {}) {
|
|
831
|
+
let results = Array.from(registry.plans.values());
|
|
832
|
+
if (options.status) {
|
|
833
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
834
|
+
results = results.filter((p2) => statuses.includes(p2.status));
|
|
835
|
+
}
|
|
836
|
+
if (options.codePattern) {
|
|
837
|
+
const pattern = new RegExp(options.codePattern, "i");
|
|
838
|
+
results = results.filter((p2) => pattern.test(p2.code));
|
|
839
|
+
}
|
|
840
|
+
if (options.namePattern) {
|
|
841
|
+
const pattern = new RegExp(options.namePattern, "i");
|
|
842
|
+
results = results.filter((p2) => pattern.test(p2.name));
|
|
843
|
+
}
|
|
844
|
+
if (options.tags && options.tags.length > 0) {
|
|
845
|
+
results = results.filter(
|
|
846
|
+
(p2) => options.tags.some((tag) => p2.tags?.includes(tag))
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (options.minProgress !== void 0) {
|
|
850
|
+
results = results.filter((p2) => p2.progress >= options.minProgress);
|
|
851
|
+
}
|
|
852
|
+
if (options.maxProgress !== void 0) {
|
|
853
|
+
results = results.filter((p2) => p2.progress <= options.maxProgress);
|
|
854
|
+
}
|
|
855
|
+
if (options.sortBy) {
|
|
856
|
+
const dir = options.sortDir === "desc" ? -1 : 1;
|
|
857
|
+
results.sort((a, b) => {
|
|
858
|
+
const aVal = a[options.sortBy];
|
|
859
|
+
const bVal = b[options.sortBy];
|
|
860
|
+
if (aVal === void 0 || aVal === null) return 1 * dir;
|
|
861
|
+
if (bVal === void 0 || bVal === null) return -1 * dir;
|
|
862
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
863
|
+
return aVal.localeCompare(bVal) * dir;
|
|
864
|
+
}
|
|
865
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
866
|
+
return (aVal - bVal) * dir;
|
|
867
|
+
}
|
|
868
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
869
|
+
return (aVal.getTime() - bVal.getTime()) * dir;
|
|
870
|
+
}
|
|
871
|
+
return 0;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
const total = results.length;
|
|
875
|
+
if (options.offset !== void 0) {
|
|
876
|
+
results = results.slice(options.offset);
|
|
877
|
+
}
|
|
878
|
+
if (options.limit !== void 0) {
|
|
879
|
+
results = results.slice(0, options.limit);
|
|
880
|
+
}
|
|
881
|
+
return { plans: results, total };
|
|
882
|
+
}
|
|
883
|
+
function getPlanByCode(registry, code) {
|
|
884
|
+
const paths = registry.byCode.get(code);
|
|
885
|
+
if (!paths || paths.length === 0) return null;
|
|
886
|
+
return registry.plans.get(paths[0]) || null;
|
|
887
|
+
}
|
|
888
|
+
function getPlanByPath(registry, path) {
|
|
889
|
+
return registry.plans.get(resolve(path)) || null;
|
|
890
|
+
}
|
|
891
|
+
function getPlansByStatus(registry, status) {
|
|
892
|
+
const paths = registry.byStatus.get(status) || [];
|
|
893
|
+
return paths.map((p2) => registry.plans.get(p2)).filter((p2) => p2 !== void 0);
|
|
894
|
+
}
|
|
895
|
+
function getRegistryStats(registry) {
|
|
896
|
+
const plans = Array.from(registry.plans.values());
|
|
897
|
+
const byStatus = {
|
|
898
|
+
pending: 0,
|
|
899
|
+
in_progress: 0,
|
|
900
|
+
completed: 0,
|
|
901
|
+
failed: 0,
|
|
902
|
+
blocked: 0,
|
|
903
|
+
skipped: 0
|
|
904
|
+
};
|
|
905
|
+
for (const plan of plans) {
|
|
906
|
+
byStatus[plan.status]++;
|
|
907
|
+
}
|
|
908
|
+
const totalProgress = plans.reduce((sum, p2) => sum + p2.progress, 0);
|
|
909
|
+
const averageProgress = plans.length > 0 ? Math.round(totalProgress / plans.length) : 0;
|
|
910
|
+
const sortedByDate = [...plans].sort(
|
|
911
|
+
(a, b) => a.registeredAt.getTime() - b.registeredAt.getTime()
|
|
912
|
+
);
|
|
913
|
+
return {
|
|
914
|
+
totalPlans: plans.length,
|
|
915
|
+
byStatus,
|
|
916
|
+
averageProgress,
|
|
917
|
+
oldestPlan: sortedByDate[0],
|
|
918
|
+
newestPlan: sortedByDate[sortedByDate.length - 1],
|
|
919
|
+
searchPathCount: registry.searchPaths.length
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function generateRetrospective(plan, options) {
|
|
923
|
+
const steps = plan.steps || [];
|
|
924
|
+
const completedSteps = steps.filter((s2) => s2.status === "completed");
|
|
925
|
+
const skippedSteps = steps.filter((s2) => s2.status === "skipped");
|
|
926
|
+
let duration;
|
|
927
|
+
if (plan.state.startedAt && plan.state.completedAt) {
|
|
928
|
+
const start = plan.state.startedAt instanceof Date ? plan.state.startedAt.getTime() : new Date(plan.state.startedAt).getTime();
|
|
929
|
+
const end = plan.state.completedAt instanceof Date ? plan.state.completedAt.getTime() : new Date(plan.state.completedAt).getTime();
|
|
930
|
+
duration = end - start;
|
|
931
|
+
}
|
|
932
|
+
const whatWentWell = options?.whatWentWell || generateWhatWentWell(plan);
|
|
933
|
+
const whatCouldImprove = options?.whatCouldImprove || generateWhatCouldImprove(plan);
|
|
934
|
+
const keyLearnings = options?.keyLearnings || [];
|
|
935
|
+
const actionItems = options?.actionItems || [];
|
|
936
|
+
return {
|
|
937
|
+
planName: plan.metadata.name,
|
|
938
|
+
planCode: plan.metadata.code,
|
|
939
|
+
startedAt: plan.state.startedAt,
|
|
940
|
+
completedAt: plan.state.completedAt,
|
|
941
|
+
duration,
|
|
942
|
+
totalSteps: steps.length,
|
|
943
|
+
completedSteps: completedSteps.length,
|
|
944
|
+
skippedSteps: skippedSteps.length,
|
|
945
|
+
whatWentWell,
|
|
946
|
+
whatCouldImprove,
|
|
947
|
+
keyLearnings,
|
|
948
|
+
actionItems,
|
|
949
|
+
stepsSummary: steps.map((s2) => ({
|
|
950
|
+
number: s2.number,
|
|
951
|
+
title: s2.title,
|
|
952
|
+
status: s2.status,
|
|
953
|
+
duration: s2.duration,
|
|
954
|
+
notes: s2.notes
|
|
955
|
+
}))
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function generateWhatWentWell(plan) {
|
|
959
|
+
const items = [];
|
|
960
|
+
const steps = plan.steps || [];
|
|
961
|
+
const completed = steps.filter((s2) => s2.status === "completed").length;
|
|
962
|
+
const rate = steps.length > 0 ? completed / steps.length * 100 : 0;
|
|
963
|
+
if (rate === 100) {
|
|
964
|
+
items.push("All steps completed successfully");
|
|
965
|
+
} else if (rate >= 90) {
|
|
966
|
+
items.push(`High completion rate (${rate.toFixed(0)}%)`);
|
|
967
|
+
}
|
|
968
|
+
if (plan.state.blockers.length === 0) {
|
|
969
|
+
items.push("No blockers encountered");
|
|
970
|
+
}
|
|
971
|
+
if (plan.feedback && plan.feedback.length > 0) {
|
|
972
|
+
items.push(`Successfully incorporated ${plan.feedback.length} feedback item(s)`);
|
|
973
|
+
}
|
|
974
|
+
return items;
|
|
975
|
+
}
|
|
976
|
+
function generateWhatCouldImprove(plan) {
|
|
977
|
+
const items = [];
|
|
978
|
+
const steps = plan.steps || [];
|
|
979
|
+
const skipped = steps.filter((s2) => s2.status === "skipped");
|
|
980
|
+
if (skipped.length > 0) {
|
|
981
|
+
items.push(`${skipped.length} step(s) were skipped - review if necessary`);
|
|
982
|
+
}
|
|
983
|
+
if (plan.state.blockers.length > 0) {
|
|
984
|
+
items.push(
|
|
985
|
+
`Address recurring blockers: ${plan.state.blockers.map((b) => b.description).join(", ")}`
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
if (plan.state.issues.length > 0) {
|
|
989
|
+
items.push(`Resolve outstanding issues before next iteration`);
|
|
990
|
+
}
|
|
991
|
+
return items;
|
|
992
|
+
}
|
|
993
|
+
function generateRetrospectiveMarkdown(retro) {
|
|
994
|
+
const lines = [];
|
|
995
|
+
lines.push(`# Retrospective: ${retro.planName}
|
|
996
|
+
`);
|
|
997
|
+
lines.push(`**Plan Code:** ${retro.planCode}
|
|
998
|
+
`);
|
|
999
|
+
if (retro.startedAt) {
|
|
1000
|
+
lines.push(`**Started:** ${formatDate(retro.startedAt)}`);
|
|
1001
|
+
}
|
|
1002
|
+
if (retro.completedAt) {
|
|
1003
|
+
lines.push(`**Completed:** ${formatDate(retro.completedAt)}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (retro.duration) {
|
|
1006
|
+
lines.push(`**Duration:** ${formatDuration(retro.duration)}`);
|
|
1007
|
+
}
|
|
1008
|
+
lines.push("");
|
|
1009
|
+
lines.push("## Summary\n");
|
|
1010
|
+
lines.push(`| Metric | Value |`);
|
|
1011
|
+
lines.push(`|--------|-------|`);
|
|
1012
|
+
lines.push(`| Total Steps | ${retro.totalSteps} |`);
|
|
1013
|
+
lines.push(`| Completed | ${retro.completedSteps} |`);
|
|
1014
|
+
lines.push(`| Skipped | ${retro.skippedSteps} |`);
|
|
1015
|
+
lines.push(
|
|
1016
|
+
`| Completion Rate | ${(retro.completedSteps / retro.totalSteps * 100).toFixed(0)}% |`
|
|
1017
|
+
);
|
|
1018
|
+
lines.push("");
|
|
1019
|
+
lines.push("## What Went Well\n");
|
|
1020
|
+
if (retro.whatWentWell.length > 0) {
|
|
1021
|
+
for (const item of retro.whatWentWell) {
|
|
1022
|
+
lines.push(`- ✅ ${item}`);
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
lines.push("*No entries yet. Add your insights here.*");
|
|
1026
|
+
}
|
|
1027
|
+
lines.push("");
|
|
1028
|
+
lines.push("## What Could Improve\n");
|
|
1029
|
+
if (retro.whatCouldImprove.length > 0) {
|
|
1030
|
+
for (const item of retro.whatCouldImprove) {
|
|
1031
|
+
lines.push(`- 🔄 ${item}`);
|
|
1032
|
+
}
|
|
1033
|
+
} else {
|
|
1034
|
+
lines.push("*No entries yet. Add your insights here.*");
|
|
1035
|
+
}
|
|
1036
|
+
lines.push("");
|
|
1037
|
+
lines.push("## Key Learnings\n");
|
|
1038
|
+
if (retro.keyLearnings.length > 0) {
|
|
1039
|
+
for (const item of retro.keyLearnings) {
|
|
1040
|
+
lines.push(`- 💡 ${item}`);
|
|
1041
|
+
}
|
|
1042
|
+
} else {
|
|
1043
|
+
lines.push("*What did you learn? Document it here.*");
|
|
1044
|
+
}
|
|
1045
|
+
lines.push("");
|
|
1046
|
+
lines.push("## Action Items\n");
|
|
1047
|
+
if (retro.actionItems.length > 0) {
|
|
1048
|
+
for (const item of retro.actionItems) {
|
|
1049
|
+
lines.push(`- [ ] ${item}`);
|
|
1050
|
+
}
|
|
1051
|
+
} else {
|
|
1052
|
+
lines.push("*Any follow-up tasks? Add them here.*");
|
|
1053
|
+
}
|
|
1054
|
+
lines.push("");
|
|
1055
|
+
lines.push("## Steps Summary\n");
|
|
1056
|
+
lines.push(`| # | Title | Status | Notes |`);
|
|
1057
|
+
lines.push(`|---|-------|--------|-------|`);
|
|
1058
|
+
for (const step of retro.stepsSummary) {
|
|
1059
|
+
const status = formatStepStatus(step.status);
|
|
1060
|
+
const notes = step.notes || "-";
|
|
1061
|
+
lines.push(`| ${step.number} | ${step.title} | ${status} | ${notes} |`);
|
|
1062
|
+
}
|
|
1063
|
+
lines.push("");
|
|
1064
|
+
lines.push("---");
|
|
1065
|
+
lines.push(`*Generated on ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}*`);
|
|
1066
|
+
return lines.join("\n");
|
|
1067
|
+
}
|
|
1068
|
+
async function createRetrospective(plan, options) {
|
|
1069
|
+
const retro = generateRetrospective(plan, options);
|
|
1070
|
+
const content = generateRetrospectiveMarkdown(retro);
|
|
1071
|
+
const retroPath = join(plan.metadata.path, "RETROSPECTIVE.md");
|
|
1072
|
+
await writeFile(retroPath, content, "utf-8");
|
|
1073
|
+
return retroPath;
|
|
1074
|
+
}
|
|
1075
|
+
function formatDate(date) {
|
|
1076
|
+
if (typeof date === "string") return date;
|
|
1077
|
+
return date.toISOString().split("T")[0];
|
|
1078
|
+
}
|
|
1079
|
+
function formatDuration(ms) {
|
|
1080
|
+
const hours = Math.floor(ms / (1e3 * 60 * 60));
|
|
1081
|
+
const minutes = Math.floor(ms % (1e3 * 60 * 60) / (1e3 * 60));
|
|
1082
|
+
const days = Math.floor(hours / 24);
|
|
1083
|
+
if (days > 0) {
|
|
1084
|
+
return `${days} day(s), ${hours % 24} hour(s)`;
|
|
1085
|
+
}
|
|
1086
|
+
if (hours > 0) {
|
|
1087
|
+
return `${hours} hour(s), ${minutes} minute(s)`;
|
|
1088
|
+
}
|
|
1089
|
+
return `${minutes} minute(s)`;
|
|
1090
|
+
}
|
|
1091
|
+
function formatStepStatus(status) {
|
|
1092
|
+
const emojis = {
|
|
1093
|
+
completed: "✅",
|
|
1094
|
+
skipped: "⏭️",
|
|
1095
|
+
pending: "⬜",
|
|
1096
|
+
in_progress: "🔄",
|
|
1097
|
+
blocked: "⏸️"
|
|
1098
|
+
};
|
|
1099
|
+
return `${emojis[status] || ""} ${status}`;
|
|
1100
|
+
}
|
|
1101
|
+
const HISTORY_FILE = ".history/HISTORY.json";
|
|
1102
|
+
function initHistory(initialVersion = "0.1") {
|
|
1103
|
+
return {
|
|
1104
|
+
revisions: [
|
|
1105
|
+
{
|
|
1106
|
+
version: initialVersion,
|
|
1107
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1108
|
+
message: "Initial version"
|
|
1109
|
+
}
|
|
1110
|
+
],
|
|
1111
|
+
currentVersion: initialVersion,
|
|
1112
|
+
milestones: []
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
async function loadHistory(planPath) {
|
|
1116
|
+
const historyPath = join(planPath, HISTORY_FILE);
|
|
1117
|
+
let history;
|
|
1118
|
+
try {
|
|
1119
|
+
const content = await readFile(historyPath, "utf-8");
|
|
1120
|
+
const data = JSON.parse(content);
|
|
1121
|
+
history = {
|
|
1122
|
+
...data,
|
|
1123
|
+
revisions: data.revisions.map((r) => ({
|
|
1124
|
+
...r,
|
|
1125
|
+
createdAt: new Date(r.createdAt)
|
|
1126
|
+
})),
|
|
1127
|
+
milestones: data.milestones?.map((m2) => ({
|
|
1128
|
+
...m2,
|
|
1129
|
+
createdAt: new Date(m2.createdAt)
|
|
1130
|
+
}))
|
|
1131
|
+
};
|
|
1132
|
+
} catch {
|
|
1133
|
+
history = initHistory();
|
|
1134
|
+
}
|
|
1135
|
+
return createHistoryManager(history, historyPath);
|
|
1136
|
+
}
|
|
1137
|
+
async function saveHistory(history, planPath) {
|
|
1138
|
+
const historyPath = join(planPath, HISTORY_FILE);
|
|
1139
|
+
await mkdir(dirname(historyPath), { recursive: true });
|
|
1140
|
+
const data = {
|
|
1141
|
+
...history,
|
|
1142
|
+
revisions: history.revisions.map((r) => ({
|
|
1143
|
+
...r,
|
|
1144
|
+
createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : r.createdAt
|
|
1145
|
+
})),
|
|
1146
|
+
milestones: history.milestones?.map((m2) => ({
|
|
1147
|
+
...m2,
|
|
1148
|
+
createdAt: m2.createdAt instanceof Date ? m2.createdAt.toISOString() : m2.createdAt
|
|
1149
|
+
}))
|
|
1150
|
+
};
|
|
1151
|
+
await writeFile(historyPath, JSON.stringify(data, null, 2), "utf-8");
|
|
1152
|
+
}
|
|
1153
|
+
function createHistoryManager(history, path) {
|
|
1154
|
+
return {
|
|
1155
|
+
history,
|
|
1156
|
+
path,
|
|
1157
|
+
async save() {
|
|
1158
|
+
const planPath = dirname(dirname(path));
|
|
1159
|
+
await saveHistory(history, planPath);
|
|
1160
|
+
},
|
|
1161
|
+
async reload() {
|
|
1162
|
+
const planPath = dirname(dirname(path));
|
|
1163
|
+
const reloaded = await loadHistory(planPath);
|
|
1164
|
+
Object.assign(history, reloaded.history);
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function createRevision(history, message, options) {
|
|
1169
|
+
const currentVersion = history.currentVersion;
|
|
1170
|
+
const [major, minor] = currentVersion.split(".").map(Number);
|
|
1171
|
+
const newVersion = `${major}.${minor + 1}`;
|
|
1172
|
+
const revision = {
|
|
1173
|
+
version: newVersion,
|
|
1174
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1175
|
+
message,
|
|
1176
|
+
author: options?.author,
|
|
1177
|
+
feedbackId: options?.feedbackId
|
|
1178
|
+
};
|
|
1179
|
+
history.revisions.push(revision);
|
|
1180
|
+
history.currentVersion = newVersion;
|
|
1181
|
+
return revision;
|
|
1182
|
+
}
|
|
1183
|
+
function getRevision(history, version) {
|
|
1184
|
+
const index = history.revisions.findIndex((r) => r.version === version);
|
|
1185
|
+
if (index === -1) return void 0;
|
|
1186
|
+
const revision = history.revisions[index];
|
|
1187
|
+
return {
|
|
1188
|
+
...revision,
|
|
1189
|
+
index,
|
|
1190
|
+
isCurrent: revision.version === history.currentVersion
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
function listRevisions(history) {
|
|
1194
|
+
return history.revisions.map((r, index) => ({
|
|
1195
|
+
...r,
|
|
1196
|
+
index,
|
|
1197
|
+
isCurrent: r.version === history.currentVersion
|
|
1198
|
+
}));
|
|
1199
|
+
}
|
|
1200
|
+
function compareRevisions(history, fromVersion, toVersion) {
|
|
1201
|
+
const from = getRevision(history, fromVersion);
|
|
1202
|
+
const to = getRevision(history, toVersion);
|
|
1203
|
+
if (!from || !to) return void 0;
|
|
1204
|
+
const fromTime = from.createdAt instanceof Date ? from.createdAt.getTime() : new Date(from.createdAt).getTime();
|
|
1205
|
+
const toTime = to.createdAt instanceof Date ? to.createdAt.getTime() : new Date(to.createdAt).getTime();
|
|
1206
|
+
return {
|
|
1207
|
+
from,
|
|
1208
|
+
to,
|
|
1209
|
+
timeDiff: toTime - fromTime,
|
|
1210
|
+
revisionCount: Math.abs(to.index - from.index)
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function getLatestRevision(history) {
|
|
1214
|
+
if (history.revisions.length === 0) return void 0;
|
|
1215
|
+
const index = history.revisions.length - 1;
|
|
1216
|
+
const revision = history.revisions[index];
|
|
1217
|
+
return {
|
|
1218
|
+
...revision,
|
|
1219
|
+
index,
|
|
1220
|
+
isCurrent: true
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function nextVersion(currentVersion) {
|
|
1224
|
+
const [major, minor] = currentVersion.split(".").map(Number);
|
|
1225
|
+
return `${major}.${minor + 1}`;
|
|
1226
|
+
}
|
|
1227
|
+
function createMilestone(history, name, description) {
|
|
1228
|
+
const milestone = {
|
|
1229
|
+
name,
|
|
1230
|
+
version: history.currentVersion,
|
|
1231
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1232
|
+
description
|
|
1233
|
+
};
|
|
1234
|
+
if (!history.milestones) {
|
|
1235
|
+
history.milestones = [];
|
|
1236
|
+
}
|
|
1237
|
+
history.milestones.push(milestone);
|
|
1238
|
+
return milestone;
|
|
1239
|
+
}
|
|
1240
|
+
function getMilestone(history, name) {
|
|
1241
|
+
if (!history.milestones) return void 0;
|
|
1242
|
+
const index = history.milestones.findIndex((m2) => m2.name === name);
|
|
1243
|
+
if (index === -1) return void 0;
|
|
1244
|
+
return {
|
|
1245
|
+
...history.milestones[index],
|
|
1246
|
+
index
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function listMilestones(history) {
|
|
1250
|
+
if (!history.milestones) return [];
|
|
1251
|
+
return history.milestones.map((m2, index) => ({
|
|
1252
|
+
...m2,
|
|
1253
|
+
index
|
|
1254
|
+
}));
|
|
1255
|
+
}
|
|
1256
|
+
function rollbackToMilestone(history, milestoneName) {
|
|
1257
|
+
const milestone = getMilestone(history, milestoneName);
|
|
1258
|
+
if (!milestone) {
|
|
1259
|
+
return {
|
|
1260
|
+
success: false,
|
|
1261
|
+
error: `Milestone not found: ${milestoneName}`
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
const currentIndex = history.revisions.findIndex(
|
|
1265
|
+
(r) => r.version === history.currentVersion
|
|
1266
|
+
);
|
|
1267
|
+
const targetIndex = history.revisions.findIndex(
|
|
1268
|
+
(r) => r.version === milestone.version
|
|
1269
|
+
);
|
|
1270
|
+
if (targetIndex === -1) {
|
|
1271
|
+
return {
|
|
1272
|
+
success: false,
|
|
1273
|
+
error: `Revision not found for milestone: ${milestone.version}`
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
const revisionsRolledBack = currentIndex - targetIndex;
|
|
1277
|
+
history.currentVersion = milestone.version;
|
|
1278
|
+
return {
|
|
1279
|
+
success: true,
|
|
1280
|
+
milestone,
|
|
1281
|
+
newVersion: milestone.version,
|
|
1282
|
+
revisionsRolledBack
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
function getLatestMilestone(history) {
|
|
1286
|
+
if (!history.milestones || history.milestones.length === 0) {
|
|
1287
|
+
return void 0;
|
|
1288
|
+
}
|
|
1289
|
+
const index = history.milestones.length - 1;
|
|
1290
|
+
return {
|
|
1291
|
+
...history.milestones[index],
|
|
1292
|
+
index
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function renderPlan(plan, options) {
|
|
1296
|
+
try {
|
|
1297
|
+
let content;
|
|
1298
|
+
switch (options.format) {
|
|
1299
|
+
case "markdown":
|
|
1300
|
+
content = renderToMarkdown(plan, options);
|
|
1301
|
+
break;
|
|
1302
|
+
case "json":
|
|
1303
|
+
content = renderToJson(plan, options);
|
|
1304
|
+
break;
|
|
1305
|
+
case "html":
|
|
1306
|
+
content = renderToHtml(plan, options);
|
|
1307
|
+
break;
|
|
1308
|
+
default:
|
|
1309
|
+
return {
|
|
1310
|
+
success: false,
|
|
1311
|
+
error: `Unknown format: ${options.format}`
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
success: true,
|
|
1316
|
+
content,
|
|
1317
|
+
format: options.format
|
|
1318
|
+
};
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
return {
|
|
1321
|
+
success: false,
|
|
1322
|
+
error: error instanceof Error ? error.message : "Unknown render error"
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
class MockStepExecutor {
|
|
1327
|
+
delay;
|
|
1328
|
+
shouldFail;
|
|
1329
|
+
constructor(options) {
|
|
1330
|
+
this.delay = options?.delay ?? 100;
|
|
1331
|
+
this.shouldFail = options?.shouldFail ?? false;
|
|
1332
|
+
}
|
|
1333
|
+
async execute(context) {
|
|
1334
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.delay));
|
|
1335
|
+
context.onProgress?.(`Executing step ${context.step.number}...`);
|
|
1336
|
+
if (this.shouldFail) {
|
|
1337
|
+
return {
|
|
1338
|
+
success: false,
|
|
1339
|
+
step: context.step.number,
|
|
1340
|
+
error: new Error("Mock execution failed"),
|
|
1341
|
+
duration: 0
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
return {
|
|
1345
|
+
success: true,
|
|
1346
|
+
step: context.step.number,
|
|
1347
|
+
output: `Completed step ${context.step.number}: ${context.step.title}`,
|
|
1348
|
+
duration: 0
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
function createExecutor(config) {
|
|
1353
|
+
switch (config.type) {
|
|
1354
|
+
case "mock":
|
|
1355
|
+
return new MockStepExecutor();
|
|
1356
|
+
case "anthropic":
|
|
1357
|
+
case "openai":
|
|
1358
|
+
case "gemini":
|
|
1359
|
+
throw new Error(
|
|
1360
|
+
`Provider '${config.type}' requires @riotprompt/execution-${config.type} package. Import and configure it directly for LLM-powered execution.`
|
|
1361
|
+
);
|
|
1362
|
+
default:
|
|
1363
|
+
throw new Error(`Unknown provider: ${config.type}`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
async function executeStep$1(executor, plan, stepNumber, options) {
|
|
1367
|
+
const step = plan.steps?.find((s2) => s2.number === stepNumber);
|
|
1368
|
+
if (!step) {
|
|
1369
|
+
return {
|
|
1370
|
+
success: false,
|
|
1371
|
+
step: stepNumber,
|
|
1372
|
+
error: new Error(`Step ${stepNumber} not found`),
|
|
1373
|
+
duration: 0
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
const context = {
|
|
1377
|
+
plan,
|
|
1378
|
+
step,
|
|
1379
|
+
provider: options?.provider ?? { type: "mock" },
|
|
1380
|
+
workingDirectory: options?.workingDirectory,
|
|
1381
|
+
env: options?.env,
|
|
1382
|
+
onProgress: options?.onProgress,
|
|
1383
|
+
onComplete: options?.onComplete
|
|
1384
|
+
};
|
|
1385
|
+
const result = await executor.execute(context);
|
|
1386
|
+
options?.onComplete?.(result);
|
|
1387
|
+
return result;
|
|
1388
|
+
}
|
|
1389
|
+
async function executePendingSteps(executor, plan, options) {
|
|
1390
|
+
const results = [];
|
|
1391
|
+
const pendingSteps = plan.steps?.filter((s2) => s2.status === "pending") ?? [];
|
|
1392
|
+
for (const step of pendingSteps) {
|
|
1393
|
+
const result = await executeStep$1(executor, plan, step.number, {
|
|
1394
|
+
provider: options?.provider,
|
|
1395
|
+
onProgress: (msg) => options?.onProgress?.(step.number, msg),
|
|
1396
|
+
onComplete: (res) => options?.onStepComplete?.(step.number, res)
|
|
1397
|
+
});
|
|
1398
|
+
results.push(result);
|
|
1399
|
+
if (!result.success && options?.stopOnError) {
|
|
1400
|
+
break;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return results;
|
|
1404
|
+
}
|
|
1405
|
+
function outputSuccess(message) {
|
|
1406
|
+
console.log(chalk.green("✓") + " " + message);
|
|
1407
|
+
}
|
|
1408
|
+
function outputError(message) {
|
|
1409
|
+
console.error(chalk.red("✗") + " " + message);
|
|
1410
|
+
}
|
|
1411
|
+
function outputWarning(message) {
|
|
1412
|
+
console.log(chalk.yellow("⚠") + " " + message);
|
|
1413
|
+
}
|
|
1414
|
+
function outputInfo(message) {
|
|
1415
|
+
console.log(chalk.blue("ℹ") + " " + message);
|
|
1416
|
+
}
|
|
1417
|
+
function getStatusIcon(status) {
|
|
1418
|
+
const icons = {
|
|
27
1419
|
pending: "⬜",
|
|
28
1420
|
in_progress: "🔄",
|
|
29
1421
|
completed: "✅",
|
|
30
1422
|
failed: "❌",
|
|
31
1423
|
blocked: "⏸️",
|
|
32
1424
|
skipped: "⏭️"
|
|
1425
|
+
};
|
|
1426
|
+
return icons[status] || "⬜";
|
|
1427
|
+
}
|
|
1428
|
+
function formatStatus(status) {
|
|
1429
|
+
const colors = {
|
|
1430
|
+
pending: chalk.dim,
|
|
1431
|
+
in_progress: chalk.yellow,
|
|
1432
|
+
completed: chalk.green,
|
|
1433
|
+
failed: chalk.red,
|
|
1434
|
+
blocked: chalk.magenta,
|
|
1435
|
+
skipped: chalk.gray
|
|
1436
|
+
};
|
|
1437
|
+
const color = colors[status] || chalk.white;
|
|
1438
|
+
return color(status.replace("_", " "));
|
|
1439
|
+
}
|
|
1440
|
+
function outputStepList(steps) {
|
|
1441
|
+
for (const step of steps) {
|
|
1442
|
+
const num = String(step.number).padStart(2, "0");
|
|
1443
|
+
const icon = getStatusIcon(step.status);
|
|
1444
|
+
const status = formatStatus(step.status);
|
|
1445
|
+
console.log(
|
|
1446
|
+
` ${icon} ${chalk.bold(num)} ${step.title} ${chalk.dim(`[${status}]`)}`
|
|
1447
|
+
);
|
|
33
1448
|
}
|
|
34
|
-
};
|
|
35
|
-
const VERSION = "0.0.1";
|
|
36
|
-
async function loadPlan(_path) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
"riotplan.loadPlan is not yet implemented. Coming in v0.1.0!"
|
|
39
|
-
);
|
|
40
1449
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1450
|
+
function outputPlanSummary(plan) {
|
|
1451
|
+
const { metadata, state, steps } = plan;
|
|
1452
|
+
const completedCount = steps.filter((s2) => s2.status === "completed").length;
|
|
1453
|
+
console.log();
|
|
1454
|
+
console.log(chalk.bold(metadata.name));
|
|
1455
|
+
console.log(chalk.dim(`Code: ${metadata.code}`));
|
|
1456
|
+
console.log();
|
|
1457
|
+
console.log(
|
|
1458
|
+
`Status: ${getStatusIcon(state.status)} ${formatStatus(state.status)}`
|
|
44
1459
|
);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
throw new Error(
|
|
48
|
-
"riotplan.parseStatus is not yet implemented. Coming in v0.1.0!"
|
|
1460
|
+
console.log(
|
|
1461
|
+
`Progress: ${state.progress}% (${completedCount}/${steps.length} steps)`
|
|
49
1462
|
);
|
|
1463
|
+
if (state.currentStep) {
|
|
1464
|
+
const currentStep = steps.find((s2) => s2.number === state.currentStep);
|
|
1465
|
+
if (currentStep) {
|
|
1466
|
+
console.log(
|
|
1467
|
+
`Current: Step ${state.currentStep} - ${currentStep.title}`
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
if (state.blockers.length > 0) {
|
|
1472
|
+
console.log();
|
|
1473
|
+
console.log(chalk.yellow(`Blockers: ${state.blockers.length}`));
|
|
1474
|
+
for (const blocker of state.blockers) {
|
|
1475
|
+
console.log(chalk.dim(` - ${blocker.description}`));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
50
1478
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1479
|
+
function outputJson(data) {
|
|
1480
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1481
|
+
}
|
|
1482
|
+
class CliError extends Error {
|
|
1483
|
+
constructor(message, code, exitCode = 1) {
|
|
1484
|
+
super(message);
|
|
1485
|
+
this.code = code;
|
|
1486
|
+
this.exitCode = exitCode;
|
|
1487
|
+
this.name = "CliError";
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function handleError(error) {
|
|
1491
|
+
if (error instanceof CliError) {
|
|
1492
|
+
console.error(chalk.red(`Error [${error.code}]: ${error.message}`));
|
|
1493
|
+
process.exit(error.exitCode);
|
|
1494
|
+
}
|
|
1495
|
+
if (error instanceof Error) {
|
|
1496
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
1497
|
+
if (process.env.DEBUG) {
|
|
1498
|
+
console.error(error.stack);
|
|
1499
|
+
}
|
|
1500
|
+
process.exit(1);
|
|
1501
|
+
}
|
|
1502
|
+
console.error(chalk.red("An unknown error occurred"));
|
|
1503
|
+
process.exit(1);
|
|
1504
|
+
}
|
|
1505
|
+
function notImplemented(feature) {
|
|
1506
|
+
console.log(chalk.yellow(`⚠ ${feature} is not yet implemented.`));
|
|
1507
|
+
console.log(chalk.dim("Check back in a future version!"));
|
|
1508
|
+
process.exit(0);
|
|
55
1509
|
}
|
|
1510
|
+
const VERSION = "0.0.1";
|
|
56
1511
|
async function executeStep(_plan, _stepNumber, _context) {
|
|
57
1512
|
throw new Error(
|
|
58
1513
|
"riotplan.executeStep is not yet implemented. Coming in v0.1.0!"
|
|
@@ -69,14 +1524,144 @@ function updatePlanState(_plan, _stepNumber, _result) {
|
|
|
69
1524
|
);
|
|
70
1525
|
}
|
|
71
1526
|
export {
|
|
1527
|
+
V as BasicTemplate,
|
|
1528
|
+
aa as CRITERIA_PATTERNS,
|
|
1529
|
+
CliError,
|
|
1530
|
+
W as FeatureTemplate,
|
|
1531
|
+
ab as HEALTH_THRESHOLDS,
|
|
1532
|
+
Y as MigrationTemplate,
|
|
1533
|
+
MockStepExecutor,
|
|
72
1534
|
PLAN_CONVENTIONS,
|
|
1535
|
+
a9 as PRIORITY_WEIGHTS,
|
|
1536
|
+
X as RefactoringTemplate,
|
|
1537
|
+
Z as SprintTemplate,
|
|
73
1538
|
VERSION,
|
|
74
|
-
|
|
1539
|
+
addRelationship,
|
|
1540
|
+
U as applyTemplate,
|
|
1541
|
+
a2 as archiveCommand,
|
|
1542
|
+
w as blockStep,
|
|
1543
|
+
F as buildDependencyGraph,
|
|
1544
|
+
G as buildDependencyGraphFromMap,
|
|
1545
|
+
ag as checkCompletion,
|
|
1546
|
+
af as checkCoverage,
|
|
1547
|
+
compareRevisions,
|
|
1548
|
+
y as completeStep,
|
|
1549
|
+
J as computeExecutionOrder,
|
|
1550
|
+
h as createAnalysisDirectory,
|
|
1551
|
+
createBidirectionalRelationship,
|
|
1552
|
+
createExecutor,
|
|
1553
|
+
k as createFeedback,
|
|
1554
|
+
createMilestone,
|
|
1555
|
+
c as createPlan,
|
|
1556
|
+
_ as createProgram,
|
|
1557
|
+
createRegistry,
|
|
1558
|
+
createRetrospective,
|
|
1559
|
+
createRevision,
|
|
1560
|
+
executePendingSteps,
|
|
75
1561
|
executeStep,
|
|
76
|
-
|
|
1562
|
+
executeStep$1 as executeStepWithExecutor,
|
|
1563
|
+
B as failStep,
|
|
1564
|
+
I as findCriticalPath,
|
|
1565
|
+
formatStatus,
|
|
1566
|
+
generateRelationshipsMarkdown,
|
|
1567
|
+
generateRetrospective,
|
|
1568
|
+
generateRetrospectiveMarkdown,
|
|
1569
|
+
o as generateStatus,
|
|
1570
|
+
getBlockedPlans,
|
|
1571
|
+
L as getBlockedSteps,
|
|
1572
|
+
getBlockingPlans,
|
|
1573
|
+
getChildPlans,
|
|
1574
|
+
ae as getCriteriaSummary,
|
|
1575
|
+
getDefaultRegistryPath,
|
|
1576
|
+
M as getDependencyChain,
|
|
1577
|
+
n as getFeedback,
|
|
1578
|
+
getInverseRelationType,
|
|
1579
|
+
getLatestMilestone,
|
|
1580
|
+
getLatestRevision,
|
|
1581
|
+
getMilestone,
|
|
1582
|
+
getParentPlan,
|
|
1583
|
+
getPlanByCode,
|
|
1584
|
+
getPlanByPath,
|
|
1585
|
+
getPlansByStatus,
|
|
1586
|
+
K as getReadySteps,
|
|
1587
|
+
getRegistryStats,
|
|
1588
|
+
getRelatedPlans,
|
|
1589
|
+
getRelationshipsByType,
|
|
1590
|
+
getRevision,
|
|
1591
|
+
getStatusIcon,
|
|
1592
|
+
Q as getTemplate,
|
|
1593
|
+
handleError,
|
|
1594
|
+
j as hasAnalysis,
|
|
1595
|
+
a0 as initCommand,
|
|
1596
|
+
initHistory,
|
|
1597
|
+
p as insertStep,
|
|
1598
|
+
m as listFeedback,
|
|
1599
|
+
listMilestones,
|
|
1600
|
+
listRevisions,
|
|
1601
|
+
O as listTemplates,
|
|
1602
|
+
S as listTemplatesByCategory,
|
|
1603
|
+
g as loadAmendmentPrompts,
|
|
1604
|
+
i as loadAnalysis,
|
|
1605
|
+
f as loadElaborationPrompts,
|
|
1606
|
+
loadHistory,
|
|
77
1607
|
loadPlan,
|
|
1608
|
+
loadRegistry,
|
|
1609
|
+
t as moveStep,
|
|
1610
|
+
nextVersion,
|
|
1611
|
+
notImplemented,
|
|
1612
|
+
outputError,
|
|
1613
|
+
outputInfo,
|
|
1614
|
+
outputJson,
|
|
1615
|
+
outputPlanSummary,
|
|
1616
|
+
outputStepList,
|
|
1617
|
+
outputSuccess,
|
|
1618
|
+
outputWarning,
|
|
1619
|
+
E as parseAllDependencies,
|
|
1620
|
+
ac as parseCriteria,
|
|
1621
|
+
ad as parseCriteriaFromContent,
|
|
1622
|
+
C as parseDependenciesFromContent,
|
|
1623
|
+
D as parseDependenciesFromFile,
|
|
1624
|
+
parseRelationshipsFromContent,
|
|
1625
|
+
parseRelationshipsFromPlan,
|
|
78
1626
|
parseStatus,
|
|
1627
|
+
refreshAllPlans,
|
|
1628
|
+
refreshPlan,
|
|
1629
|
+
registerPlan,
|
|
1630
|
+
$ as registerPlanCommands,
|
|
1631
|
+
a7 as registerRenderCommands,
|
|
1632
|
+
R as registerTemplate,
|
|
1633
|
+
removeRelationship,
|
|
1634
|
+
q as removeStep,
|
|
1635
|
+
a8 as renderCommand,
|
|
1636
|
+
renderPlan,
|
|
1637
|
+
renderToHtml,
|
|
1638
|
+
renderToJson,
|
|
1639
|
+
renderToMarkdown,
|
|
79
1640
|
resumePlan,
|
|
80
|
-
|
|
1641
|
+
rollbackToMilestone,
|
|
1642
|
+
e as saveAmendmentPrompt,
|
|
1643
|
+
d as saveElaborationPrompt,
|
|
1644
|
+
saveHistory,
|
|
1645
|
+
s as saveInitialPrompt,
|
|
1646
|
+
saveRegistry,
|
|
1647
|
+
scanForPlans,
|
|
1648
|
+
searchPlans,
|
|
1649
|
+
T as searchTemplatesByTag,
|
|
1650
|
+
A as skipStep,
|
|
1651
|
+
z as startStep,
|
|
1652
|
+
a3 as templateCommand,
|
|
1653
|
+
a4 as templateListCommand,
|
|
1654
|
+
a5 as templateShowCommand,
|
|
1655
|
+
a6 as templateUseCommand,
|
|
1656
|
+
x as unblockStep,
|
|
1657
|
+
unregisterPlan,
|
|
1658
|
+
updatePlanRelationships,
|
|
1659
|
+
updatePlanState,
|
|
1660
|
+
u as updateStatus,
|
|
1661
|
+
N as updateStepDependencies,
|
|
1662
|
+
a1 as validateCommand,
|
|
1663
|
+
H as validateDependencies,
|
|
1664
|
+
v as validatePlan,
|
|
1665
|
+
validateRelationships
|
|
81
1666
|
};
|
|
82
1667
|
//# sourceMappingURL=index.js.map
|