@agent-plan/core 0.1.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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/export-service.d.ts +8 -0
- package/dist/export-service.d.ts.map +1 -0
- package/dist/export-service.js +126 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/naming.d.ts +14 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +44 -0
- package/dist/plan-store.d.ts +140 -0
- package/dist/plan-store.d.ts.map +1 -0
- package/dist/plan-store.js +1029 -0
- package/dist/render-utils.d.ts +6 -0
- package/dist/render-utils.d.ts.map +1 -0
- package/dist/render-utils.js +49 -0
- package/dist/renderer.d.ts +12 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +374 -0
- package/dist/schema.d.ts +2826 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +244 -0
- package/package.json +47 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AcceptedDecision } from "./schema.js";
|
|
2
|
+
export declare function bullet(lines: string[], indent?: number): string;
|
|
3
|
+
export declare function statusBadge(status: string): string;
|
|
4
|
+
export declare function statusIcon(status: string): string;
|
|
5
|
+
export declare function renderAcceptedDecisions(decisions: AcceptedDecision[]): string[];
|
|
6
|
+
//# sourceMappingURL=render-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-utils.d.ts","sourceRoot":"","sources":["../src/render-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,SAAI,GAAG,MAAM,CAI1D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAclD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcjD;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAU/E"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function bullet(lines, indent = 0) {
|
|
2
|
+
return lines.length > 0
|
|
3
|
+
? lines.map((l) => `${" ".repeat(indent)}- ${l}`).join("\n")
|
|
4
|
+
: `${" ".repeat(indent)}- _none_`;
|
|
5
|
+
}
|
|
6
|
+
export function statusBadge(status) {
|
|
7
|
+
const icons = {
|
|
8
|
+
draft: "π",
|
|
9
|
+
discovery: "π",
|
|
10
|
+
planned: "π",
|
|
11
|
+
"in-progress": "π§",
|
|
12
|
+
done: "β
",
|
|
13
|
+
blocked: "π«",
|
|
14
|
+
canceled: "β",
|
|
15
|
+
rejected: "β",
|
|
16
|
+
deferred: "βΈοΈ",
|
|
17
|
+
waiting: "β³",
|
|
18
|
+
};
|
|
19
|
+
return `${icons[status] ?? "β"} \`${status}\``;
|
|
20
|
+
}
|
|
21
|
+
export function statusIcon(status) {
|
|
22
|
+
const icons = {
|
|
23
|
+
draft: "π",
|
|
24
|
+
discovery: "π",
|
|
25
|
+
planned: "π",
|
|
26
|
+
"in-progress": "π§",
|
|
27
|
+
done: "β
",
|
|
28
|
+
blocked: "π«",
|
|
29
|
+
canceled: "β",
|
|
30
|
+
rejected: "β",
|
|
31
|
+
deferred: "βΈοΈ",
|
|
32
|
+
waiting: "β³",
|
|
33
|
+
};
|
|
34
|
+
return icons[status] ?? "β";
|
|
35
|
+
}
|
|
36
|
+
export function renderAcceptedDecisions(decisions) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
for (const entry of decisions) {
|
|
39
|
+
lines.push(`- **${entry.title}**`);
|
|
40
|
+
if (entry.decision)
|
|
41
|
+
lines.push(` - Decision: ${entry.decision}`);
|
|
42
|
+
if (entry.rationale)
|
|
43
|
+
lines.push(` - Rationale: ${entry.rationale}`);
|
|
44
|
+
if (entry.implementationNotes)
|
|
45
|
+
lines.push(` - Implementation: ${entry.implementationNotes}`);
|
|
46
|
+
lines.push(` - Accepted at: ${entry.acceptedAt}`);
|
|
47
|
+
}
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Feature, Phase, PlanWorkspace } from "./schema.js";
|
|
2
|
+
export declare class PlanRenderer {
|
|
3
|
+
renderPlan(plan: PlanWorkspace): string;
|
|
4
|
+
renderPhase(phase: Phase): string;
|
|
5
|
+
private renderPhaseHeading;
|
|
6
|
+
private renderPhaseBlock;
|
|
7
|
+
private renderPhaseBody;
|
|
8
|
+
renderFeature(feature: Feature, phases: Phase[]): string;
|
|
9
|
+
/** Render markdown and return a map of relative paths β content */
|
|
10
|
+
render(plan: PlanWorkspace): Map<string, string>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,OAAO,EAAE,KAAK,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAmBtG,qBAAa,YAAY;IACvB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM;IA2KvC,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM;IAIjC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,gBAAgB;IA6BxB,OAAO,CAAC,eAAe;IA2GvB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM;IAmDxD,mEAAmE;IACnE,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAejD"}
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { bullet, statusBadge, statusIcon, renderAcceptedDecisions } from "./render-utils.js";
|
|
2
|
+
function seq(value) {
|
|
3
|
+
return String(value && value > 0 ? value : 0).padStart(3, "0");
|
|
4
|
+
}
|
|
5
|
+
function featureLabel(feature) {
|
|
6
|
+
return `F${seq(feature.number)} β ${feature.name}`;
|
|
7
|
+
}
|
|
8
|
+
function phaseLabel(phase) {
|
|
9
|
+
return `P${seq(phase.number)} β ${phase.title}`;
|
|
10
|
+
}
|
|
11
|
+
function taskLabel(task) {
|
|
12
|
+
return `T${seq(task.number)} β ${task.title}`;
|
|
13
|
+
}
|
|
14
|
+
export class PlanRenderer {
|
|
15
|
+
renderPlan(plan) {
|
|
16
|
+
const { project, manifest, features, requirements, phases } = plan;
|
|
17
|
+
const lines = [];
|
|
18
|
+
lines.push(`# ${project.name} β Project Plan`);
|
|
19
|
+
lines.push("");
|
|
20
|
+
lines.push(`> ${project.goal || "*Goal not defined yet.*"}`);
|
|
21
|
+
if (project.description) {
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push(project.description);
|
|
24
|
+
}
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push(`**Last updated:** ${manifest.updatedAt}`);
|
|
27
|
+
lines.push(`**Version:** ${manifest.schemaVersion}`);
|
|
28
|
+
lines.push(`**Project ID:** \`${manifest.projectId}\``);
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("---");
|
|
31
|
+
lines.push("");
|
|
32
|
+
// ββ Scope ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
33
|
+
if (project.scope.length > 0 || project.outOfScope.length > 0) {
|
|
34
|
+
lines.push("## Scope");
|
|
35
|
+
lines.push("");
|
|
36
|
+
if (project.scope.length > 0) {
|
|
37
|
+
lines.push("### In scope");
|
|
38
|
+
lines.push(bullet(project.scope));
|
|
39
|
+
lines.push("");
|
|
40
|
+
}
|
|
41
|
+
if (project.outOfScope.length > 0) {
|
|
42
|
+
lines.push("### Out of scope");
|
|
43
|
+
lines.push(bullet(project.outOfScope));
|
|
44
|
+
lines.push("");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ββ Stack & Tools ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
48
|
+
if (project.technologies.length > 0 || project.tools.length > 0) {
|
|
49
|
+
lines.push("## Stack & Tools");
|
|
50
|
+
lines.push("");
|
|
51
|
+
if (project.technologies.length > 0) {
|
|
52
|
+
lines.push("### Technologies");
|
|
53
|
+
lines.push(bullet(project.technologies));
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
if (project.tools.length > 0) {
|
|
57
|
+
lines.push("### Tools");
|
|
58
|
+
lines.push(bullet(project.tools));
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ββ Decisions ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
63
|
+
if (project.decisions.length > 0) {
|
|
64
|
+
lines.push("## Architectural Decisions");
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push(bullet(project.decisions));
|
|
67
|
+
lines.push("");
|
|
68
|
+
}
|
|
69
|
+
if (project.acceptedDecisions.length > 0) {
|
|
70
|
+
lines.push("## Accepted Decisions");
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(...renderAcceptedDecisions(project.acceptedDecisions));
|
|
73
|
+
lines.push("");
|
|
74
|
+
}
|
|
75
|
+
// ββ Global Rules βββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
76
|
+
if (project.globalRules.length > 0) {
|
|
77
|
+
lines.push("## Global Rules");
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push(bullet(project.globalRules));
|
|
80
|
+
lines.push("");
|
|
81
|
+
}
|
|
82
|
+
// ββ Workflow Rules βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
83
|
+
const wr = project.workflowRules;
|
|
84
|
+
lines.push("## Workflow Rules");
|
|
85
|
+
lines.push("");
|
|
86
|
+
if (wr.beforePhaseStart.length > 0) {
|
|
87
|
+
lines.push("### Before phase start");
|
|
88
|
+
lines.push(bullet(wr.beforePhaseStart));
|
|
89
|
+
lines.push("");
|
|
90
|
+
}
|
|
91
|
+
if (wr.beforeTaskStart.length > 0) {
|
|
92
|
+
lines.push("### Before task start");
|
|
93
|
+
lines.push(bullet(wr.beforeTaskStart));
|
|
94
|
+
lines.push("");
|
|
95
|
+
}
|
|
96
|
+
if (wr.afterPhaseComplete.length > 0) {
|
|
97
|
+
lines.push("### After phase complete");
|
|
98
|
+
lines.push(bullet(wr.afterPhaseComplete));
|
|
99
|
+
lines.push("");
|
|
100
|
+
}
|
|
101
|
+
// ββ Features ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
102
|
+
if (features.features.length > 0) {
|
|
103
|
+
lines.push("---");
|
|
104
|
+
lines.push("## Features");
|
|
105
|
+
lines.push("");
|
|
106
|
+
for (const feature of features.features) {
|
|
107
|
+
lines.push(`### ${statusIcon(feature.status)} ${feature.id} β ${featureLabel(feature)}`);
|
|
108
|
+
if (feature.description)
|
|
109
|
+
lines.push("", feature.description);
|
|
110
|
+
lines.push("");
|
|
111
|
+
lines.push(`Status: ${statusBadge(feature.status)}`);
|
|
112
|
+
const featurePhases = phases.filter((p) => feature.phaseIds.includes(p.id));
|
|
113
|
+
if (featurePhases.length > 0) {
|
|
114
|
+
lines.push("");
|
|
115
|
+
lines.push("**Phases:**");
|
|
116
|
+
for (const fp of featurePhases) {
|
|
117
|
+
const doneTasks = fp.tasks.filter((t) => t.status === "done").length;
|
|
118
|
+
lines.push(`- ${statusIcon(fp.status)} **${fp.id}** ${phaseLabel(fp)} (${doneTasks}/${fp.tasks.length} tasks)`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (feature.workDone)
|
|
122
|
+
lines.push("", `**Work done:** ${feature.workDone}`);
|
|
123
|
+
if (feature.workRemaining)
|
|
124
|
+
lines.push("", `**Work remaining:** ${feature.workRemaining}`);
|
|
125
|
+
if (feature.acceptedDecisions.length > 0) {
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push("**Accepted decisions:**");
|
|
128
|
+
lines.push(...renderAcceptedDecisions(feature.acceptedDecisions));
|
|
129
|
+
}
|
|
130
|
+
lines.push("");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ββ Requirements βββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
134
|
+
lines.push("---");
|
|
135
|
+
lines.push("## Requirements");
|
|
136
|
+
lines.push("");
|
|
137
|
+
if (requirements.requirements.length === 0) {
|
|
138
|
+
lines.push("_No requirements defined yet._");
|
|
139
|
+
lines.push("");
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
for (const req of requirements.requirements) {
|
|
143
|
+
lines.push(`### ${req.id} β ${req.title}`);
|
|
144
|
+
if (req.description) {
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push(req.description);
|
|
147
|
+
}
|
|
148
|
+
lines.push("");
|
|
149
|
+
lines.push(`Status: ${statusBadge(req.status)}`);
|
|
150
|
+
if (req.linkedPhaseIds.length > 0) {
|
|
151
|
+
lines.push(`Phases: ${req.linkedPhaseIds.map((p) => `\`${p}\``).join(", ")}`);
|
|
152
|
+
}
|
|
153
|
+
if (req.macroTasks.length > 0) {
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push("**Macro tasks:**");
|
|
156
|
+
for (const mt of req.macroTasks) {
|
|
157
|
+
lines.push(`- ${statusIcon(mt.status)} **${mt.id}** ${mt.title}`);
|
|
158
|
+
if (mt.description)
|
|
159
|
+
lines.push(` - ${mt.description}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
lines.push("");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ββ Phases βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
166
|
+
lines.push("---");
|
|
167
|
+
lines.push("## Phases");
|
|
168
|
+
lines.push("");
|
|
169
|
+
if (phases.length === 0) {
|
|
170
|
+
lines.push("_No phases defined yet._");
|
|
171
|
+
lines.push("");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
for (const phase of phases) {
|
|
175
|
+
lines.push(this.renderPhaseBlock(phase));
|
|
176
|
+
lines.push("");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return lines.join("\n");
|
|
180
|
+
}
|
|
181
|
+
renderPhase(phase) {
|
|
182
|
+
return ["# " + this.renderPhaseHeading(phase), "", ...this.renderPhaseBody(phase)].join("\n");
|
|
183
|
+
}
|
|
184
|
+
renderPhaseHeading(phase) {
|
|
185
|
+
return `${phase.id} β ${phaseLabel(phase)}`;
|
|
186
|
+
}
|
|
187
|
+
renderPhaseBlock(phase) {
|
|
188
|
+
const lines = [];
|
|
189
|
+
lines.push(`### ${statusIcon(phase.status)} ${phase.id} β ${phaseLabel(phase)}`);
|
|
190
|
+
if (phase.summary) {
|
|
191
|
+
lines.push("");
|
|
192
|
+
lines.push(phase.summary);
|
|
193
|
+
}
|
|
194
|
+
lines.push("");
|
|
195
|
+
lines.push(`Status: ${statusBadge(phase.status)}`);
|
|
196
|
+
if (phase.dependencies.length > 0) {
|
|
197
|
+
lines.push(`Dependencies: ${phase.dependencies.join(", ")}`);
|
|
198
|
+
}
|
|
199
|
+
if (phase.completionCriteria.length > 0) {
|
|
200
|
+
lines.push("");
|
|
201
|
+
lines.push("**Completion criteria:**");
|
|
202
|
+
lines.push(bullet(phase.completionCriteria));
|
|
203
|
+
}
|
|
204
|
+
if (phase.acceptedDecisions.length > 0) {
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push("**Accepted decisions:**");
|
|
207
|
+
lines.push(...renderAcceptedDecisions(phase.acceptedDecisions));
|
|
208
|
+
}
|
|
209
|
+
if (phase.tasks.length > 0) {
|
|
210
|
+
lines.push("");
|
|
211
|
+
lines.push(`**Tasks:** ${phase.tasks.filter((t) => t.status === "done").length}/${phase.tasks.length}`);
|
|
212
|
+
}
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
renderPhaseBody(phase) {
|
|
216
|
+
const lines = [];
|
|
217
|
+
lines.push(`**Status:** ${statusBadge(phase.status)}`);
|
|
218
|
+
lines.push(`**Created:** ${phase.createdAt}`);
|
|
219
|
+
lines.push(`**Updated:** ${phase.updatedAt}`);
|
|
220
|
+
lines.push("");
|
|
221
|
+
if (phase.summary) {
|
|
222
|
+
lines.push(phase.summary);
|
|
223
|
+
lines.push("");
|
|
224
|
+
}
|
|
225
|
+
if (phase.description) {
|
|
226
|
+
lines.push(phase.description);
|
|
227
|
+
lines.push("");
|
|
228
|
+
}
|
|
229
|
+
// Goals & non-goals
|
|
230
|
+
if (phase.goals.length > 0) {
|
|
231
|
+
lines.push("## Goals");
|
|
232
|
+
lines.push(bullet(phase.goals));
|
|
233
|
+
lines.push("");
|
|
234
|
+
}
|
|
235
|
+
if (phase.nonGoals.length > 0) {
|
|
236
|
+
lines.push("## Non-goals");
|
|
237
|
+
lines.push(bullet(phase.nonGoals));
|
|
238
|
+
lines.push("");
|
|
239
|
+
}
|
|
240
|
+
if (phase.dependencies.length > 0) {
|
|
241
|
+
lines.push("## Dependencies");
|
|
242
|
+
lines.push(bullet(phase.dependencies));
|
|
243
|
+
lines.push("");
|
|
244
|
+
}
|
|
245
|
+
if (phase.risks.length > 0) {
|
|
246
|
+
lines.push("## Risks");
|
|
247
|
+
lines.push(bullet(phase.risks));
|
|
248
|
+
lines.push("");
|
|
249
|
+
}
|
|
250
|
+
if (phase.openQuestions.length > 0) {
|
|
251
|
+
lines.push("## Open Questions");
|
|
252
|
+
lines.push(bullet(phase.openQuestions));
|
|
253
|
+
lines.push("");
|
|
254
|
+
}
|
|
255
|
+
if (phase.acceptedDecisions.length > 0) {
|
|
256
|
+
lines.push("## Accepted Decisions");
|
|
257
|
+
lines.push(...renderAcceptedDecisions(phase.acceptedDecisions));
|
|
258
|
+
lines.push("");
|
|
259
|
+
}
|
|
260
|
+
// Completion criteria
|
|
261
|
+
if (phase.completionCriteria.length > 0) {
|
|
262
|
+
lines.push("## Completion Criteria");
|
|
263
|
+
lines.push(bullet(phase.completionCriteria));
|
|
264
|
+
lines.push("");
|
|
265
|
+
}
|
|
266
|
+
// Tasks
|
|
267
|
+
lines.push("## Tasks");
|
|
268
|
+
lines.push("");
|
|
269
|
+
if (phase.tasks.length === 0) {
|
|
270
|
+
lines.push("_No tasks defined._");
|
|
271
|
+
lines.push("");
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
for (const task of phase.tasks) {
|
|
275
|
+
lines.push(`### ${statusIcon(task.status)} ${task.id} β ${taskLabel(task)}`);
|
|
276
|
+
lines.push("");
|
|
277
|
+
lines.push(`Status: ${statusBadge(task.status)}`);
|
|
278
|
+
if (task.description) {
|
|
279
|
+
lines.push("");
|
|
280
|
+
lines.push(task.description);
|
|
281
|
+
}
|
|
282
|
+
if (task.notes) {
|
|
283
|
+
lines.push("");
|
|
284
|
+
lines.push(`**Implementation notes:** ${task.notes}`);
|
|
285
|
+
}
|
|
286
|
+
if (task.decisions.length > 0) {
|
|
287
|
+
lines.push("");
|
|
288
|
+
lines.push("**Decisions:**");
|
|
289
|
+
lines.push(bullet(task.decisions));
|
|
290
|
+
}
|
|
291
|
+
if (task.acceptedDecisions.length > 0) {
|
|
292
|
+
lines.push("");
|
|
293
|
+
lines.push("**Accepted decisions:**");
|
|
294
|
+
lines.push(...renderAcceptedDecisions(task.acceptedDecisions));
|
|
295
|
+
}
|
|
296
|
+
if (task.checklist.length > 0) {
|
|
297
|
+
lines.push("");
|
|
298
|
+
lines.push("**Checklist:**");
|
|
299
|
+
for (const item of task.checklist) {
|
|
300
|
+
lines.push(`- [${item.checked ? "x" : " "}] ${item.title}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (task.subtasks.length > 0) {
|
|
304
|
+
lines.push("");
|
|
305
|
+
lines.push("**Subtasks:**");
|
|
306
|
+
for (const st of task.subtasks) {
|
|
307
|
+
lines.push(`- ${statusIcon(st.status)} **${st.id}** ${st.title}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
lines.push("");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return lines;
|
|
314
|
+
}
|
|
315
|
+
renderFeature(feature, phases) {
|
|
316
|
+
const lines = [];
|
|
317
|
+
lines.push(`# ${feature.id} β ${feature.name}`);
|
|
318
|
+
lines.push("");
|
|
319
|
+
lines.push(`Status: ${statusBadge(feature.status)}`);
|
|
320
|
+
if (feature.startDate)
|
|
321
|
+
lines.push(`**Start:** ${feature.startDate}`);
|
|
322
|
+
if (feature.endDate)
|
|
323
|
+
lines.push(`**End:** ${feature.endDate}`);
|
|
324
|
+
lines.push("");
|
|
325
|
+
if (feature.description) {
|
|
326
|
+
lines.push(feature.description);
|
|
327
|
+
lines.push("");
|
|
328
|
+
}
|
|
329
|
+
if (feature.workDone) {
|
|
330
|
+
lines.push("## Work Done");
|
|
331
|
+
lines.push(feature.workDone);
|
|
332
|
+
lines.push("");
|
|
333
|
+
}
|
|
334
|
+
if (feature.workRemaining) {
|
|
335
|
+
lines.push("## Work Remaining");
|
|
336
|
+
lines.push(feature.workRemaining);
|
|
337
|
+
lines.push("");
|
|
338
|
+
}
|
|
339
|
+
if (feature.acceptedDecisions.length > 0) {
|
|
340
|
+
lines.push("## Accepted Decisions");
|
|
341
|
+
lines.push(...renderAcceptedDecisions(feature.acceptedDecisions));
|
|
342
|
+
lines.push("");
|
|
343
|
+
}
|
|
344
|
+
const featurePhases = phases.filter((p) => feature.phaseIds.includes(p.id));
|
|
345
|
+
if (featurePhases.length > 0) {
|
|
346
|
+
lines.push("## Phases");
|
|
347
|
+
lines.push("");
|
|
348
|
+
for (const fp of featurePhases) {
|
|
349
|
+
lines.push(`### ${statusIcon(fp.status)} ${fp.id} β ${fp.title}`);
|
|
350
|
+
lines.push("");
|
|
351
|
+
lines.push(`Status: ${statusBadge(fp.status)}`);
|
|
352
|
+
if (fp.summary)
|
|
353
|
+
lines.push("", fp.summary);
|
|
354
|
+
const doneTasks = fp.tasks.filter((t) => t.status === "done").length;
|
|
355
|
+
if (fp.tasks.length > 0)
|
|
356
|
+
lines.push("", `**Tasks:** ${doneTasks}/${fp.tasks.length} done`);
|
|
357
|
+
lines.push("");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return lines.join("\n");
|
|
361
|
+
}
|
|
362
|
+
/** Render markdown and return a map of relative paths β content */
|
|
363
|
+
render(plan) {
|
|
364
|
+
const files = new Map();
|
|
365
|
+
files.set("PLAN.md", this.renderPlan(plan));
|
|
366
|
+
for (const feature of plan.features.features) {
|
|
367
|
+
files.set(`features/${feature.id}.md`, this.renderFeature(feature, plan.phases));
|
|
368
|
+
}
|
|
369
|
+
for (const phase of plan.phases) {
|
|
370
|
+
files.set(`phases/${phase.id}.md`, this.renderPhase(phase));
|
|
371
|
+
}
|
|
372
|
+
return files;
|
|
373
|
+
}
|
|
374
|
+
}
|