@ulpi/cli 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
  3. package/dist/{auth-BFFBUJUC.js → auth-HDK7ECJL.js} +2 -1
  4. package/dist/{chunk-RJIRWQJD.js → chunk-3BCW6ABU.js} +402 -142
  5. package/dist/{chunk-L3PWNHSA.js → chunk-3WB5CXH4.js} +180 -5
  6. package/dist/{chunk-K4OVPFY2.js → chunk-4UCJIAOU.js} +2 -2
  7. package/dist/chunk-4XTHZVDS.js +109 -0
  8. package/dist/chunk-4ZPOZULQ.js +6522 -0
  9. package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
  10. package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
  11. package/dist/{chunk-AV5RB3N2.js → chunk-76D3BYJD.js} +48 -0
  12. package/dist/{chunk-DOIKS6C5.js → chunk-AWOSRA5F.js} +1 -1
  13. package/dist/{chunk-UCMT5OKP.js → chunk-BFEKZZHM.js} +274 -57
  14. package/dist/chunk-C7CLUQI6.js +1286 -0
  15. package/dist/{chunk-ELTGWMDE.js → chunk-E3B5NROU.js} +7 -7
  16. package/dist/chunk-EJ7TW77N.js +1418 -0
  17. package/dist/{chunk-6OURRFP7.js → chunk-IV6MWETF.js} +383 -168
  18. package/dist/chunk-IZPJHSPX.js +1478 -0
  19. package/dist/chunk-JLHNLM3C.js +228 -0
  20. package/dist/{chunk-P2RESJRN.js → chunk-KYYI23AQ.js} +2 -2
  21. package/dist/chunk-S6ANCSYO.js +1271 -0
  22. package/dist/chunk-SEU7WWNQ.js +1251 -0
  23. package/dist/chunk-SNQ7NAIS.js +453 -0
  24. package/dist/{ulpi-RMMCUAGP-EWYUE7RU.js → chunk-TSLDGT5O.js} +73 -35
  25. package/dist/{chunk-EIWYSP3A.js → chunk-UXHCHOWQ.js} +83 -62
  26. package/dist/chunk-V2H5D6Y3.js +146 -0
  27. package/dist/{chunk-5SCG7UYM.js → chunk-VVEDXI7E.js} +1 -1
  28. package/dist/chunk-VXH5Y4FO.js +6761 -0
  29. package/dist/chunk-WED4LM5N.js +322 -0
  30. package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
  31. package/dist/chunk-Z53CAR7G.js +298 -0
  32. package/dist/{ci-JQ56YIKC.js → ci-X3U2W4HC.js} +124 -26
  33. package/dist/cloud-2F3NLVHN.js +274 -0
  34. package/dist/{codemap-HMYBXJL2.js → codemap-XNGMAF3F.js} +37 -37
  35. package/dist/codex-MB5YTMRT.js +132 -0
  36. package/dist/{config-YYWEN7U2.js → config-OOELBYTH.js} +1 -1
  37. package/dist/dist-2BJYR5EI.js +59 -0
  38. package/dist/dist-3EIQTZHT.js +1380 -0
  39. package/dist/{dist-WAMAQVPK.js → dist-4U5L2X2C.js} +2 -2
  40. package/dist/{dist-4XTJ6HLM.js → dist-54KAMNLO.js} +16 -15
  41. package/dist/dist-6M4MZWZW.js +58 -0
  42. package/dist/dist-6X576SU2.js +27 -0
  43. package/dist/dist-7QOEYLFX.js +103 -0
  44. package/dist/dist-AYBGHEDY.js +2541 -0
  45. package/dist/dist-EK45QNEM.js +45 -0
  46. package/dist/{dist-U7ZIJMZD.js → dist-FKFEJRPX.js} +16 -15
  47. package/dist/dist-GTEJUBBT.js +66 -0
  48. package/dist/dist-HA74OKJZ.js +40 -0
  49. package/dist/{dist-XG2GG5SD.js → dist-HU5RZAON.js} +14 -2
  50. package/dist/dist-IYE3OBRB.js +374 -0
  51. package/dist/{dist-7WLLPWWB.js → dist-JLU26AB6.js} +12 -9
  52. package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
  53. package/dist/dist-NUEMFZFL.js +33 -0
  54. package/dist/{dist-GWGTAHNM.js → dist-NUXMDXZ3.js} +31 -3
  55. package/dist/{dist-5R4RYNQO.js → dist-YCNWHSLN.js} +15 -5
  56. package/dist/{dist-6MFVWIFF.js → dist-YFFG2ZD6.js} +9 -16
  57. package/dist/dist-ZG4OKCSR.js +15 -0
  58. package/dist/doctor-SI4LLLDZ.js +345 -0
  59. package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
  60. package/dist/{history-RNUWO4JZ.js → history-5NE46ZAH.js} +7 -7
  61. package/dist/{hooks-installer-K2JXEBNN.js → hooks-installer-UN5JZLDQ.js} +2 -2
  62. package/dist/index.js +394 -618
  63. package/dist/{init-NQWFZPKO.js → init-5FK3VKRT.js} +76 -10
  64. package/dist/job-HIDMAFW2.js +376 -0
  65. package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
  66. package/dist/kiro-VMUHDFGK.js +153 -0
  67. package/dist/{launchd-OYXUAVW6.js → launchd-6AWT54HR.js} +9 -17
  68. package/dist/mcp-PDUD7SGP.js +249 -0
  69. package/dist/mcp-installer-PQU3XOGO.js +259 -0
  70. package/dist/mcp-setup-OA7IB3H3.js +263 -0
  71. package/dist/{memory-D6ZFFCI2.js → memory-ZNAEAK3B.js} +17 -17
  72. package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
  73. package/dist/{openai-E7G2YAHU-IG33BFYF.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
  74. package/dist/portal-JYWVHXDU.js +210 -0
  75. package/dist/prd-Q4J5NVAR.js +408 -0
  76. package/dist/repos-WWZXNN3P.js +271 -0
  77. package/dist/review-integration-5WHEJU2A.js +14 -0
  78. package/dist/{rules-3OFGWHP4.js → rules-Y4VSOY5Y.js} +3 -3
  79. package/dist/run-VPNXEIBY.js +687 -0
  80. package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
  81. package/dist/server-KKSETHDV-XSSLEENT.js +20 -0
  82. package/dist/{skills-GY2CTPWN.js → skills-QEYU2N27.js} +4 -2
  83. package/dist/start-JYOEL7AJ.js +303 -0
  84. package/dist/{status-SE43TIFJ.js → status-BHQYYGAL.js} +2 -2
  85. package/dist/{templates-O2XDKB5R.js → templates-CBRUJ66V.js} +6 -5
  86. package/dist/tui-DP7736EX.js +61 -0
  87. package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
  88. package/dist/{uninstall-KWGSGZTI.js → uninstall-ICUV6DDV.js} +3 -3
  89. package/dist/{update-QYZA4D23.js → update-7ZMAYRBH.js} +3 -3
  90. package/dist/{version-checker-MVB74DEX.js → version-checker-4ZFMZA7Y.js} +2 -2
  91. package/package.json +39 -31
  92. package/dist/chunk-26LLDX2T.js +0 -553
  93. package/dist/chunk-DDRLI6JU.js +0 -331
  94. package/dist/chunk-IFATANHR.js +0 -453
  95. package/dist/chunk-JWUUVXIV.js +0 -13694
  96. package/dist/chunk-LD52XG3X.js +0 -4273
  97. package/dist/chunk-MIAQVCFW.js +0 -39
  98. package/dist/chunk-YYZOFYS6.js +0 -415
  99. package/dist/dist-XD4YI27T.js +0 -26
  100. package/dist/mcp-installer-TOYDP77X.js +0 -124
  101. package/dist/projects-COUJP4ZC.js +0 -271
  102. package/dist/review-KMGP2S25.js +0 -152
  103. package/dist/server-USLHY6GH-F4JSXCWA.js +0 -18
  104. package/dist/server-X5P6WH2M-ULZF5WHZ.js +0 -11
  105. package/dist/skills/ulpi-generate-guardian/SKILL.md +0 -750
  106. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +0 -849
  107. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +0 -591
  108. package/dist/ui-4SM2SUI6.js +0 -167
  109. package/dist/ui.html +0 -698
@@ -0,0 +1,1380 @@
1
+ import "./chunk-4VNS5WPM.js";
2
+
3
+ // ../../packages/prd-engine/dist/index.js
4
+ var TITLE_PATTERN = /^#\s+(?:PRD:\s*)?(.+)$/;
5
+ var TASK_HEADING_PATTERN = /^#{2,4}\s+(?:Task:\s*|(?:TASK|US|STORY|EPIC)-\d{1,4}:\s*)(.+)$/i;
6
+ var ID_HEADING_PATTERN = /^#{2,4}\s+((?:TASK|US|STORY|EPIC)-\d{1,4}):\s*(.+)$/i;
7
+ var CHECKLIST_PATTERN = /^[-*]\s+\[[\sx]\]\s+(.+)$/;
8
+ var AC_SECTION_PATTERN = /^#{2,5}\s+Acceptance\s+Criteria|^\*\*Acceptance\s+Criteria[:\*]*\*\*/i;
9
+ var DEPENDS_PATTERN = /\*{0,2}(?:depends?\s+on|depends|prerequisites?|requires):?\*{0,2}\s*(.+)/i;
10
+ var PAREN_DEPENDS_PATTERN = /\(depends?\s+on\s+([^)]+)\)/i;
11
+ var HTML_DEPENDS_PATTERN = /<!--\s*depends?:\s*(.+?)\s*-->/i;
12
+ var LABEL_PATTERN = /\[([a-zA-Z0-9_-]+)\]/g;
13
+ var PRIORITY_PATTERN = /(?:\*\*Priority:\*\*\s*P?|priority:\s*P?)(\d)/i;
14
+ var GWT_PATTERN = /^(?:Given|When|Then|And|But)\s+/i;
15
+ function extractTitle(markdown) {
16
+ const lines = markdown.split("\n");
17
+ for (const line of lines) {
18
+ const match = line.match(TITLE_PATTERN);
19
+ if (match) {
20
+ return match[1]?.trim() ?? "Untitled PRD";
21
+ }
22
+ }
23
+ return "Untitled PRD";
24
+ }
25
+ function extractDescription(markdown) {
26
+ const overviewMatch = markdown.match(
27
+ /^##\s+Overview\s*\n+([\s\S]*?)(?=\n##|\n---|\n$)/m
28
+ );
29
+ if (overviewMatch?.[1]) {
30
+ return overviewMatch[1].trim();
31
+ }
32
+ const lines = markdown.split("\n");
33
+ let foundTitle = false;
34
+ const descLines = [];
35
+ for (const line of lines) {
36
+ if (line.match(TITLE_PATTERN)) {
37
+ foundTitle = true;
38
+ continue;
39
+ }
40
+ if (foundTitle && !line.startsWith("#") && !line.startsWith(">")) {
41
+ if (line.trim()) {
42
+ descLines.push(line.trim());
43
+ } else if (descLines.length > 0) {
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ return descLines.join(" ").trim() || "No description";
49
+ }
50
+ function extractSections(markdown) {
51
+ const lines = markdown.split("\n");
52
+ const sections = [];
53
+ let currentSection = null;
54
+ for (let i = 0; i < lines.length; i++) {
55
+ const line = lines[i] ?? "";
56
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
57
+ if (headingMatch) {
58
+ if (currentSection) {
59
+ const content = lines.slice(currentSection.startLine + 1, i).join("\n").trim();
60
+ sections.push({
61
+ heading: currentSection.heading,
62
+ level: currentSection.level,
63
+ content,
64
+ tasks: []
65
+ });
66
+ }
67
+ currentSection = {
68
+ heading: headingMatch[2]?.trim() ?? "",
69
+ level: headingMatch[1]?.length ?? 1,
70
+ startLine: i
71
+ };
72
+ }
73
+ }
74
+ if (currentSection) {
75
+ const content = lines.slice(currentSection.startLine + 1).join("\n").trim();
76
+ sections.push({
77
+ heading: currentSection.heading,
78
+ level: currentSection.level,
79
+ content,
80
+ tasks: []
81
+ });
82
+ }
83
+ return sections;
84
+ }
85
+ function generateTaskId(prefix, index) {
86
+ return `${prefix}${String(index).padStart(3, "0")}`;
87
+ }
88
+ function extractCriteria(text) {
89
+ const criteria = [];
90
+ const lines = text.split("\n");
91
+ let inAcSection = false;
92
+ for (const line of lines) {
93
+ const trimmed = line.trim();
94
+ if (AC_SECTION_PATTERN.test(trimmed)) {
95
+ inAcSection = true;
96
+ continue;
97
+ }
98
+ if (inAcSection && /^#{2,5}\s+/.test(trimmed) && !AC_SECTION_PATTERN.test(trimmed)) {
99
+ break;
100
+ }
101
+ if (inAcSection && /^\*\*(?!Acceptance)/.test(trimmed)) {
102
+ break;
103
+ }
104
+ if (inAcSection) {
105
+ const checkMatch = trimmed.match(CHECKLIST_PATTERN);
106
+ if (checkMatch?.[1]) {
107
+ criteria.push(checkMatch[1].trim());
108
+ continue;
109
+ }
110
+ const bulletMatch = trimmed.match(/^[-*]\s+(.+)$/);
111
+ if (bulletMatch?.[1] && !bulletMatch[1].startsWith("[")) {
112
+ criteria.push(bulletMatch[1].trim());
113
+ continue;
114
+ }
115
+ const numberedMatch = trimmed.match(/^\d+\.\s+(.+)$/);
116
+ if (numberedMatch?.[1]) {
117
+ criteria.push(numberedMatch[1].trim());
118
+ continue;
119
+ }
120
+ if (GWT_PATTERN.test(trimmed)) {
121
+ criteria.push(trimmed);
122
+ continue;
123
+ }
124
+ }
125
+ }
126
+ if (criteria.length === 0) {
127
+ for (const line of lines) {
128
+ const acMatch = line.trim().match(/^[-*]\s+\[AC\]\s+(.+)$/i);
129
+ if (acMatch?.[1]) {
130
+ criteria.push(acMatch[1].trim());
131
+ }
132
+ }
133
+ }
134
+ return criteria;
135
+ }
136
+ function extractLabels(text) {
137
+ const labels = [];
138
+ let match;
139
+ LABEL_PATTERN.lastIndex = 0;
140
+ while ((match = LABEL_PATTERN.exec(text)) !== null) {
141
+ const label = match[1];
142
+ if (label && !["x", " ", "AC"].includes(label)) {
143
+ labels.push(label);
144
+ }
145
+ }
146
+ return [...new Set(labels)];
147
+ }
148
+ var TASK_ID_PATTERN = /^(?:TASK|US|STORY|EPIC)-\d{1,4}$/i;
149
+ function cleanDepRef(raw) {
150
+ const cleaned = raw.replace(/\*{1,2}/g, "").replace(/`/g, "").trim();
151
+ if (!cleaned || /^[\s(]*none|^[\s(]*n\/?a|^[\s(]*\(/i.test(cleaned)) {
152
+ return null;
153
+ }
154
+ if (!TASK_ID_PATTERN.test(cleaned)) {
155
+ return null;
156
+ }
157
+ return cleaned.toUpperCase();
158
+ }
159
+ function extractDependencies(text) {
160
+ const deps = [];
161
+ const lines = text.split("\n");
162
+ for (const line of lines) {
163
+ const inlineMatch = line.match(DEPENDS_PATTERN);
164
+ if (inlineMatch?.[1]) {
165
+ const parsed = inlineMatch[1].split(/[,;]\s*/).map(cleanDepRef).filter((d) => d !== null);
166
+ deps.push(...parsed);
167
+ }
168
+ const parenMatch = line.match(PAREN_DEPENDS_PATTERN);
169
+ if (parenMatch?.[1]) {
170
+ const parsed = parenMatch[1].split(/[,;]\s*/).map(cleanDepRef).filter((d) => d !== null);
171
+ deps.push(...parsed);
172
+ }
173
+ const htmlMatch = line.match(HTML_DEPENDS_PATTERN);
174
+ if (htmlMatch?.[1]) {
175
+ const parsed = htmlMatch[1].split(/[,;]\s*/).map(cleanDepRef).filter((d) => d !== null);
176
+ deps.push(...parsed);
177
+ }
178
+ }
179
+ return [...new Set(deps)];
180
+ }
181
+ function extractPriority(text) {
182
+ const match = text.match(PRIORITY_PATTERN);
183
+ if (match?.[1]) {
184
+ const p = parseInt(match[1], 10);
185
+ if (!isNaN(p) && p >= 1 && p <= 4) {
186
+ return p;
187
+ }
188
+ }
189
+ return 2;
190
+ }
191
+ function extractDependencyMap(markdown) {
192
+ const sectionMatch = markdown.match(
193
+ /##\s+Task\s+Dependencies\s*\n+```json\s*\n([\s\S]*?)\n```/i
194
+ );
195
+ if (!sectionMatch?.[1]) {
196
+ return null;
197
+ }
198
+ try {
199
+ const parsed = JSON.parse(sectionMatch[1]);
200
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
201
+ return null;
202
+ }
203
+ const depMap = /* @__PURE__ */ new Map();
204
+ for (const [taskId, deps] of Object.entries(parsed)) {
205
+ if (Array.isArray(deps)) {
206
+ depMap.set(
207
+ taskId.toUpperCase(),
208
+ deps.filter((d) => typeof d === "string" && d.length > 0).map((d) => d.toUpperCase())
209
+ );
210
+ }
211
+ }
212
+ return depMap.size > 0 ? depMap : null;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ function extractTasks(markdown, sections, prefix) {
218
+ const tasks = [];
219
+ let taskCounter = 0;
220
+ const lines = markdown.split("\n");
221
+ let currentTask = null;
222
+ for (let i = 0; i < lines.length; i++) {
223
+ const line = lines[i] ?? "";
224
+ const idMatch = line.match(ID_HEADING_PATTERN);
225
+ if (idMatch) {
226
+ if (currentTask) {
227
+ const sectionText = lines.slice(currentTask.startLine + 1, i).join("\n");
228
+ tasks.push(buildTask(currentTask.id, currentTask.title, sectionText));
229
+ }
230
+ currentTask = {
231
+ id: idMatch[1]?.toUpperCase() ?? "",
232
+ title: idMatch[2]?.trim() ?? "",
233
+ startLine: i
234
+ };
235
+ continue;
236
+ }
237
+ const taskMatch = line.match(TASK_HEADING_PATTERN);
238
+ if (taskMatch && !idMatch) {
239
+ if (currentTask) {
240
+ const sectionText = lines.slice(currentTask.startLine + 1, i).join("\n");
241
+ tasks.push(buildTask(currentTask.id, currentTask.title, sectionText));
242
+ }
243
+ taskCounter++;
244
+ currentTask = {
245
+ id: generateTaskId(prefix, taskCounter),
246
+ title: taskMatch[1]?.trim() ?? "",
247
+ startLine: i
248
+ };
249
+ }
250
+ }
251
+ if (currentTask) {
252
+ const sectionText = lines.slice(currentTask.startLine + 1).join("\n");
253
+ tasks.push(buildTask(currentTask.id, currentTask.title, sectionText));
254
+ }
255
+ if (tasks.length === 0) {
256
+ const tasksSections = sections.filter(
257
+ (s) => /^tasks?$/i.test(s.heading.trim())
258
+ );
259
+ for (const section of tasksSections) {
260
+ const sectionLines = section.content.split("\n");
261
+ for (const sectionLine of sectionLines) {
262
+ const checkMatch = sectionLine.match(CHECKLIST_PATTERN);
263
+ if (checkMatch?.[1]) {
264
+ taskCounter++;
265
+ tasks.push({
266
+ id: generateTaskId(prefix, taskCounter),
267
+ title: checkMatch[1].trim(),
268
+ description: checkMatch[1].trim(),
269
+ acceptanceCriteria: [],
270
+ dependsOn: extractDependencies(checkMatch[1]),
271
+ priority: taskCounter,
272
+ labels: extractLabels(checkMatch[1])
273
+ });
274
+ }
275
+ }
276
+ }
277
+ }
278
+ if (tasks.length === 0) {
279
+ const tasksSections = sections.filter(
280
+ (s) => /^tasks?$/i.test(s.heading.trim())
281
+ );
282
+ for (const section of tasksSections) {
283
+ const sectionLines = section.content.split("\n");
284
+ for (const sectionLine of sectionLines) {
285
+ const numberedMatch = sectionLine.match(/^\d+\.\s+(.+)$/);
286
+ if (numberedMatch?.[1]) {
287
+ taskCounter++;
288
+ tasks.push({
289
+ id: generateTaskId(prefix, taskCounter),
290
+ title: numberedMatch[1].trim(),
291
+ description: numberedMatch[1].trim(),
292
+ acceptanceCriteria: [],
293
+ dependsOn: extractDependencies(numberedMatch[1]),
294
+ priority: taskCounter,
295
+ labels: extractLabels(numberedMatch[1])
296
+ });
297
+ }
298
+ }
299
+ }
300
+ }
301
+ return tasks;
302
+ }
303
+ function buildTask(id, title, sectionText) {
304
+ const descLines = [];
305
+ const textLines = sectionText.split("\n");
306
+ for (const line of textLines) {
307
+ const trimmed = line.trim();
308
+ if (!trimmed && descLines.length > 0) break;
309
+ if (AC_SECTION_PATTERN.test(trimmed)) break;
310
+ if (/^\*\*(?:Acceptance|Priority|Depends|Labels|Notes):/.test(trimmed)) break;
311
+ if (/^#{2,5}\s+/.test(trimmed)) break;
312
+ if (trimmed) {
313
+ descLines.push(trimmed.replace(/^\*\*Description:\*\*\s*/, ""));
314
+ }
315
+ }
316
+ const description = descLines.join(" ").replace(/\*\*([^*]+)\*\*/g, "$1").trim() || title;
317
+ return {
318
+ id,
319
+ title,
320
+ description,
321
+ acceptanceCriteria: extractCriteria(sectionText),
322
+ dependsOn: extractDependencies(sectionText),
323
+ priority: extractPriority(sectionText),
324
+ labels: extractLabels(sectionText)
325
+ };
326
+ }
327
+ function parsePrdMarkdown(markdown, options = {}) {
328
+ const warnings = [];
329
+ const prefix = options.taskPrefix ?? "TASK-";
330
+ const title = extractTitle(markdown);
331
+ const description = extractDescription(markdown);
332
+ const sections = extractSections(markdown);
333
+ const tasks = extractTasks(markdown, sections, prefix);
334
+ if (tasks.length === 0) {
335
+ warnings.push(`No tasks found in PRD. Expected headings like "## Task: Title" or checklist items under a "Tasks" section.`);
336
+ }
337
+ const depMap = extractDependencyMap(markdown);
338
+ if (depMap) {
339
+ for (const task of tasks) {
340
+ const deps = depMap.get(task.id.toUpperCase());
341
+ if (deps !== void 0) {
342
+ task.dependsOn = deps;
343
+ }
344
+ }
345
+ }
346
+ for (const task of tasks) {
347
+ if (task.acceptanceCriteria.length === 0) {
348
+ warnings.push(`Task ${task.id} (${task.title}) has no acceptance criteria.`);
349
+ }
350
+ }
351
+ const sectionsWithTasks = sections.map((section) => ({
352
+ ...section,
353
+ tasks: tasks.filter(
354
+ (t) => section.content.includes(t.title) || section.content.includes(t.id)
355
+ )
356
+ }));
357
+ const metadata = {};
358
+ const branchMatch = markdown.match(/^>\s*Branch:\s*`?([^`\n]+)`?/m);
359
+ if (branchMatch?.[1]) {
360
+ metadata.branchName = branchMatch[1].trim();
361
+ }
362
+ const dateMatch = markdown.match(/^>\s*Generated:\s*(.+)$/m);
363
+ if (dateMatch?.[1]) {
364
+ metadata.createdAt = dateMatch[1].trim();
365
+ }
366
+ const document = {
367
+ title,
368
+ description,
369
+ sections: sectionsWithTasks,
370
+ tasks,
371
+ metadata
372
+ };
373
+ return { document, warnings };
374
+ }
375
+ var PRD_CHAT_PROMPT = `You are an expert product engineer helping create a Product Requirements Document (PRD) through conversation.
376
+
377
+ ## Conversation Strategy
378
+
379
+ 1. **Progressive refinement** \u2014 ask 3-5 adaptive clarifying questions, one set at a time. Do NOT ask all questions upfront.
380
+ 2. **Adapt to answers** \u2014 each follow-up question set should build on previous responses. Dig deeper into areas of ambiguity or risk.
381
+ 3. **Infer context** \u2014 if the user provides a brief description, infer reasonable defaults and confirm them rather than asking from scratch.
382
+ 4. **Know when to stop** \u2014 after 2-3 rounds of questions, generate the PRD. Do not over-question.
383
+
384
+ ## Question Categories (pick 3-5 per round)
385
+
386
+ - **Scope:** What exactly should this feature do? What is explicitly out of scope?
387
+ - **Users:** Who uses this? What is their current workflow?
388
+ - **Technical:** Are there existing systems this integrates with? What constraints exist?
389
+ - **Dependencies:** What must be built first? Are there external dependencies?
390
+ - **Risks:** What could go wrong? What are the edge cases?
391
+
392
+ ## PRD Output Format
393
+
394
+ When you have enough context, generate the complete PRD wrapped in [PRD]...[/PRD] markers.
395
+
396
+ The PRD must include:
397
+ - Title: \`# PRD: Feature Name\`
398
+ - \`## Overview\` section with description and target users
399
+ - \`## Tasks\` section with structured task headings
400
+
401
+ Each task must follow this format:
402
+
403
+ \`\`\`
404
+ ### TASK-NNN: Title
405
+
406
+ Description of what this task involves, scoped to specific files or areas.
407
+
408
+ **Acceptance Criteria:**
409
+ - [ ] Criterion 1
410
+ - [ ] Criterion 2
411
+
412
+ **Priority:** P1
413
+ \`\`\`
414
+
415
+ After ALL tasks, add a \`## Task Dependencies\` section with a JSON block declaring ALL dependencies:
416
+
417
+ \`\`\`
418
+ ## Task Dependencies
419
+
420
+ \\\`\\\`\\\`json
421
+ {
422
+ "TASK-001": [],
423
+ "TASK-002": ["TASK-001"],
424
+ "TASK-003": ["TASK-001", "TASK-002"],
425
+ "TASK-004": [],
426
+ "TASK-005": ["TASK-004"]
427
+ }
428
+ \\\`\\\`\\\`
429
+ \`\`\`
430
+
431
+ CRITICAL: The \`## Task Dependencies\` JSON block is REQUIRED. It is machine-parsed to schedule parallel execution. Every task ID must appear as a key. Tasks with no dependencies use an empty array \`[]\`. Dependencies list task IDs that MUST complete before that task can start.
432
+
433
+ ## Task Design Principles
434
+
435
+ - **Decompose into discrete tasks** \u2014 each task should be completable independently (given its dependencies).
436
+ - **Assign priorities P1-P4:** P1 = critical path, P2 = important, P3 = nice-to-have, P4 = future.
437
+ - **Group for parallel execution** \u2014 independent tasks with no mutual dependencies can run in parallel.
438
+ - **Map dependencies explicitly** \u2014 in the Task Dependencies JSON block. Over-constraining reduces parallelism.
439
+ - **Scope to specific areas** \u2014 reference file paths, modules, or components in task descriptions.
440
+
441
+ The user can respond with shorthand for quick iteration.
442
+ `;
443
+ var PRD_GENERATION_PROMPT = `You are an autonomous product engineer generating a Product Requirements Document (PRD).
444
+
445
+ ## Your Mission
446
+
447
+ You will receive a feature description. Your job is to:
448
+
449
+ 1. **Explore the codebase first** \u2014 read package.json, key config files, directory structure, and relevant source files to understand the project architecture.
450
+ 2. **Understand the existing patterns** \u2014 identify frameworks, conventions, file organization, and testing patterns in use.
451
+ 3. **Decompose the feature** into well-scoped tasks that reference specific files and areas found in the codebase.
452
+ 4. **Map dependencies** using actual file and module relationships you discover.
453
+ 5. **Write the PRD file** to the specified output path using the Write tool.
454
+
455
+ ## Important Rules
456
+
457
+ - Work **autonomously** \u2014 do NOT ask questions or wait for interaction.
458
+ - Base your task decomposition on **actual codebase analysis**, not assumptions.
459
+ - Each task should reference **specific files, directories, or modules** you found during exploration.
460
+ - Ensure the output is **parseable** by the PRD parser (follow the exact format below).
461
+
462
+ ## PRD Output Format
463
+
464
+ Write a markdown file with this exact structure:
465
+
466
+ \`\`\`markdown
467
+ # PRD: Feature Name
468
+
469
+ ## Overview
470
+
471
+ High-level description of the feature, its purpose, and target users.
472
+
473
+ ## Tasks
474
+
475
+ ### TASK-001: Title
476
+
477
+ Description scoped to specific files/areas found in the codebase.
478
+
479
+ **Acceptance Criteria:**
480
+ - [ ] Criterion 1
481
+ - [ ] Criterion 2
482
+
483
+ **Priority:** P1
484
+
485
+ ---
486
+
487
+ ### TASK-002: Title
488
+
489
+ Description...
490
+
491
+ **Acceptance Criteria:**
492
+ - [ ] Criterion 1
493
+
494
+ **Priority:** P1
495
+
496
+ ---
497
+
498
+ ## Task Dependencies
499
+
500
+ \\\`\\\`\\\`json
501
+ {
502
+ "TASK-001": [],
503
+ "TASK-002": ["TASK-001"]
504
+ }
505
+ \\\`\\\`\\\`
506
+ \`\`\`
507
+
508
+ CRITICAL: The \`## Task Dependencies\` section with the JSON block is REQUIRED at the end of the PRD. It is machine-parsed to schedule parallel execution. Every task ID must appear as a key. Tasks with no dependencies use an empty array \`[]\`. Dependencies list task IDs that MUST complete before that task can start.
509
+
510
+ ## Task Design Principles
511
+
512
+ - **Atomic tasks** \u2014 each task should be completable in a single focused session. If a task has >3 acceptance criteria, consider splitting it.
513
+ - **File-scoped** \u2014 reference specific files or directories in the description (e.g., "Update \`src/api/routes.ts\` to add...").
514
+ - **Priority P1-P4:** P1 = must-have for MVP, P2 = important, P3 = enhancement, P4 = future/optional.
515
+ - **Parallel-friendly** \u2014 tasks with no mutual dependencies can run in parallel.
516
+ - **Dependency accuracy** \u2014 only declare dependencies that are truly blocking. Over-constraining reduces parallelism.
517
+ `;
518
+ function getPrdSystemPrompt() {
519
+ return PRD_CHAT_PROMPT;
520
+ }
521
+ function getPrdGenerationPrompt() {
522
+ return PRD_GENERATION_PROMPT;
523
+ }
524
+ function buildPrdGenerationPrompt(featureDescription, options) {
525
+ const parts = [];
526
+ parts.push("## Instructions\n");
527
+ parts.push(PRD_GENERATION_PROMPT);
528
+ parts.push("");
529
+ parts.push("## Feature Request\n");
530
+ parts.push(featureDescription);
531
+ parts.push("");
532
+ const outputDir = options?.outputDir ?? ".ulpi/prds";
533
+ const slug = options?.slug ?? "generated-prd";
534
+ const outputPath = `${outputDir}/prd-${slug}.md`;
535
+ parts.push("## Output\n");
536
+ parts.push(`Write the PRD to: \`${outputPath}\``);
537
+ parts.push("Use the Write tool to create the file at this path.");
538
+ return parts.join("\n");
539
+ }
540
+ function detectPrd(response) {
541
+ const markerMatch = response.match(/\[PRD\]([\s\S]+?)\[\/PRD\]/);
542
+ if (markerMatch?.[1]) {
543
+ const content = markerMatch[1].trim();
544
+ const nameMatch = content.match(/^#\s+(?:PRD:\s*)?(.+?)[\n\r]/m);
545
+ return {
546
+ found: true,
547
+ content,
548
+ featureName: nameMatch?.[1]?.trim() ?? "Untitled Feature"
549
+ };
550
+ }
551
+ const prdMatch = response.match(/# PRD:\s*(.+?)[\n\r]([\s\S]+)$/);
552
+ if (prdMatch?.[1] && prdMatch[2]) {
553
+ return {
554
+ found: true,
555
+ content: `# PRD: ${prdMatch[1]}
556
+ ${prdMatch[2]}`.trim(),
557
+ featureName: prdMatch[1].trim()
558
+ };
559
+ }
560
+ return { found: false };
561
+ }
562
+ function buildConversationPrompt(history, userMessage, maxHistory = 50) {
563
+ const parts = [];
564
+ parts.push("## Instructions\n");
565
+ parts.push(PRD_CHAT_PROMPT);
566
+ parts.push("");
567
+ const historySlice = history.slice(-maxHistory);
568
+ if (historySlice.length > 0) {
569
+ parts.push("## Conversation History\n");
570
+ for (const msg of historySlice) {
571
+ if (msg.role === "user") {
572
+ parts.push(`User: ${msg.content}`);
573
+ } else if (msg.role === "assistant") {
574
+ parts.push(`Assistant: ${msg.content}`);
575
+ }
576
+ }
577
+ parts.push("");
578
+ }
579
+ parts.push("## Current Request\n");
580
+ parts.push(userMessage);
581
+ return parts.join("\n");
582
+ }
583
+ function slugify(text) {
584
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
585
+ }
586
+ function renderPrdMarkdown(doc) {
587
+ const lines = [];
588
+ lines.push(`# PRD: ${doc.title}`);
589
+ lines.push("");
590
+ if (doc.metadata.createdAt) {
591
+ lines.push(`> Generated: ${doc.metadata.createdAt}`);
592
+ }
593
+ if (doc.metadata.branchName) {
594
+ lines.push(`> Branch: \`${doc.metadata.branchName}\``);
595
+ }
596
+ if (doc.metadata.createdAt || doc.metadata.branchName) {
597
+ lines.push("");
598
+ }
599
+ lines.push("## Overview");
600
+ lines.push("");
601
+ lines.push(doc.description);
602
+ lines.push("");
603
+ if (doc.tasks.length > 0) {
604
+ lines.push("## Tasks");
605
+ lines.push("");
606
+ for (const task of doc.tasks) {
607
+ lines.push(`### ${task.id}: ${task.title}`);
608
+ lines.push("");
609
+ lines.push(task.description);
610
+ lines.push("");
611
+ if (task.acceptanceCriteria.length > 0) {
612
+ lines.push("**Acceptance Criteria:**");
613
+ for (const criterion of task.acceptanceCriteria) {
614
+ lines.push(`- [ ] ${criterion}`);
615
+ }
616
+ lines.push("");
617
+ }
618
+ if (task.labels.length > 0) {
619
+ lines.push(`**Labels:** ${task.labels.map((l) => `[${l}]`).join(" ")}`);
620
+ lines.push("");
621
+ }
622
+ lines.push(`**Priority:** P${task.priority}`);
623
+ lines.push("");
624
+ lines.push("---");
625
+ lines.push("");
626
+ }
627
+ const depObj = {};
628
+ for (const task of doc.tasks) {
629
+ depObj[task.id] = [...task.dependsOn];
630
+ }
631
+ lines.push("## Task Dependencies");
632
+ lines.push("");
633
+ lines.push("```json");
634
+ lines.push(JSON.stringify(depObj, null, 2));
635
+ lines.push("```");
636
+ lines.push("");
637
+ }
638
+ return lines.join("\n");
639
+ }
640
+ function processPrdResponse(response) {
641
+ const detection = detectPrd(response);
642
+ if (!detection.found || !detection.content) {
643
+ return null;
644
+ }
645
+ const parsed = parsePrdMarkdown(detection.content);
646
+ return parsed.document;
647
+ }
648
+ function extractFilePaths(text) {
649
+ const patterns = [
650
+ /`([^`]+\.[a-zA-Z]{1,5})`/g,
651
+ // `path/to/file.ext`
652
+ /(?:^|\s)([\w./\\-]+\.[a-zA-Z]{2,5})/gm,
653
+ // bare file.ext references
654
+ /(?:src|lib|app|packages?|modules?)\/[\w./-]+/g
655
+ // src/... style paths
656
+ ];
657
+ const paths = /* @__PURE__ */ new Set();
658
+ for (const pattern of patterns) {
659
+ let match;
660
+ pattern.lastIndex = 0;
661
+ while ((match = pattern.exec(text)) !== null) {
662
+ const candidate = match[1] ?? match[0];
663
+ if (candidate && candidate.length > 2 && !candidate.startsWith("http")) {
664
+ paths.add(candidate.trim());
665
+ }
666
+ }
667
+ }
668
+ return Array.from(paths);
669
+ }
670
+ function hasCyclicDeps(tasks) {
671
+ const taskIds = new Set(tasks.map((t) => t.id));
672
+ const visited = /* @__PURE__ */ new Set();
673
+ const inStack = /* @__PURE__ */ new Set();
674
+ function dfs(id) {
675
+ if (inStack.has(id)) return true;
676
+ if (visited.has(id)) return false;
677
+ visited.add(id);
678
+ inStack.add(id);
679
+ const task = tasks.find((t) => t.id === id);
680
+ if (task) {
681
+ for (const dep of task.dependsOn) {
682
+ if (taskIds.has(dep) && dfs(dep)) return true;
683
+ }
684
+ }
685
+ inStack.delete(id);
686
+ return false;
687
+ }
688
+ for (const task of tasks) {
689
+ if (dfs(task.id)) return true;
690
+ }
691
+ return false;
692
+ }
693
+ function computeParallelism(tasks) {
694
+ if (tasks.length === 0) return 0;
695
+ const taskIds = new Set(tasks.map((t) => t.id));
696
+ const independent = tasks.filter(
697
+ (t) => t.dependsOn.length === 0 || t.dependsOn.every((d) => !taskIds.has(d))
698
+ );
699
+ return independent.length / tasks.length;
700
+ }
701
+ function scoreTaskAtomicity(tasks) {
702
+ if (tasks.length === 0) {
703
+ return { name: "Task Atomicity", score: 0, found: false, details: "No tasks found" };
704
+ }
705
+ let totalPenalty = 0;
706
+ const issues = [];
707
+ for (const task of tasks) {
708
+ let penalty = 0;
709
+ if (task.acceptanceCriteria.length > 3) {
710
+ penalty += Math.min(30, (task.acceptanceCriteria.length - 3) * 10);
711
+ issues.push(`${task.id} has ${task.acceptanceCriteria.length} criteria (consider splitting)`);
712
+ }
713
+ if (task.description.length > 500) {
714
+ penalty += Math.min(20, Math.floor((task.description.length - 500) / 100) * 5);
715
+ }
716
+ const vaguePatterns = /^(implement|build|create|add|update|fix)\s+(the\s+)?feature$/i;
717
+ if (vaguePatterns.test(task.title)) {
718
+ penalty += 20;
719
+ issues.push(`${task.id} has a vague title`);
720
+ }
721
+ totalPenalty += penalty;
722
+ }
723
+ const avgPenalty = totalPenalty / tasks.length;
724
+ const score = Math.max(0, Math.round(100 - avgPenalty));
725
+ return {
726
+ name: "Task Atomicity",
727
+ score,
728
+ found: true,
729
+ details: issues.length > 0 ? issues.slice(0, 3).join("; ") : "Tasks are well-scoped"
730
+ };
731
+ }
732
+ function scoreTaskDecoupling(tasks) {
733
+ if (tasks.length <= 1) {
734
+ return { name: "Task Decoupling", score: 100, found: true, details: "Single task or empty" };
735
+ }
736
+ const taskFiles = /* @__PURE__ */ new Map();
737
+ for (const task of tasks) {
738
+ const fullText = `${task.title} ${task.description} ${task.acceptanceCriteria.join(" ")}`;
739
+ const files = extractFilePaths(fullText);
740
+ taskFiles.set(task.id, new Set(files));
741
+ }
742
+ let overlapCount = 0;
743
+ let pairCount = 0;
744
+ const taskList = Array.from(taskFiles.entries());
745
+ for (let i = 0; i < taskList.length; i++) {
746
+ for (let j = i + 1; j < taskList.length; j++) {
747
+ pairCount++;
748
+ const filesA = taskList[i][1];
749
+ const filesB = taskList[j][1];
750
+ for (const file of filesA) {
751
+ if (filesB.has(file)) {
752
+ overlapCount++;
753
+ break;
754
+ }
755
+ }
756
+ }
757
+ }
758
+ if (pairCount === 0) {
759
+ return { name: "Task Decoupling", score: 100, found: true };
760
+ }
761
+ const overlapRatio = overlapCount / pairCount;
762
+ const score = Math.max(0, Math.round(100 - overlapRatio * 100));
763
+ return {
764
+ name: "Task Decoupling",
765
+ score,
766
+ found: true,
767
+ details: overlapCount > 0 ? `${overlapCount} task pair(s) reference the same files` : "No file overlap between tasks"
768
+ };
769
+ }
770
+ function scoreDependencyClarity(tasks) {
771
+ if (tasks.length === 0) {
772
+ return { name: "Dependency Clarity", score: 0, found: false, details: "No tasks found" };
773
+ }
774
+ const taskIds = new Set(tasks.map((t) => t.id));
775
+ const issues = [];
776
+ let danglingCount = 0;
777
+ for (const task of tasks) {
778
+ for (const dep of task.dependsOn) {
779
+ if (!taskIds.has(dep)) {
780
+ danglingCount++;
781
+ issues.push(`${task.id} depends on non-existent ${dep}`);
782
+ }
783
+ }
784
+ }
785
+ const hasCycles = hasCyclicDeps(tasks);
786
+ if (hasCycles) {
787
+ issues.push("Circular dependency detected");
788
+ }
789
+ let score = 100;
790
+ score -= danglingCount * 15;
791
+ if (hasCycles) score -= 30;
792
+ score = Math.max(0, Math.round(score));
793
+ return {
794
+ name: "Dependency Clarity",
795
+ score,
796
+ found: true,
797
+ details: issues.length > 0 ? issues.slice(0, 3).join("; ") : "All dependencies are valid"
798
+ };
799
+ }
800
+ function scoreTaskScope(tasks) {
801
+ if (tasks.length === 0) {
802
+ return { name: "Task Scope", score: 0, found: false, details: "No tasks found" };
803
+ }
804
+ let scopedCount = 0;
805
+ for (const task of tasks) {
806
+ const fullText = `${task.title} ${task.description}`;
807
+ const files = extractFilePaths(fullText);
808
+ const hasSpecificRef = files.length > 0 || /(?:module|component|package|service|handler|controller|route|model|schema|hook|util)/i.test(fullText);
809
+ if (hasSpecificRef) {
810
+ scopedCount++;
811
+ }
812
+ }
813
+ const ratio = scopedCount / tasks.length;
814
+ const score = Math.round(ratio * 100);
815
+ return {
816
+ name: "Task Scope",
817
+ score,
818
+ found: true,
819
+ details: `${scopedCount}/${tasks.length} tasks reference specific files or areas`
820
+ };
821
+ }
822
+ function scoreParallelPotential(tasks) {
823
+ if (tasks.length <= 1) {
824
+ return { name: "Parallel Potential", score: 100, found: true, details: "Single task" };
825
+ }
826
+ const parallelism = computeParallelism(tasks);
827
+ const score = Math.round(parallelism * 100);
828
+ const taskIds = new Set(tasks.map((t) => t.id));
829
+ const independent = tasks.filter(
830
+ (t) => t.dependsOn.length === 0 || t.dependsOn.every((d) => !taskIds.has(d))
831
+ );
832
+ return {
833
+ name: "Parallel Potential",
834
+ score,
835
+ found: true,
836
+ details: `${independent.length}/${tasks.length} tasks can run independently`
837
+ };
838
+ }
839
+ function scoreAcceptanceCriteria(tasks) {
840
+ if (tasks.length === 0) {
841
+ return { name: "Acceptance Criteria", score: 0, found: false, details: "No tasks found" };
842
+ }
843
+ let totalScore = 0;
844
+ const issues = [];
845
+ for (const task of tasks) {
846
+ if (task.acceptanceCriteria.length === 0) {
847
+ issues.push(`${task.id} has no acceptance criteria`);
848
+ } else if (task.acceptanceCriteria.length === 1) {
849
+ totalScore += 50;
850
+ } else {
851
+ totalScore += 100;
852
+ }
853
+ }
854
+ const score = Math.round(totalScore / tasks.length);
855
+ return {
856
+ name: "Acceptance Criteria",
857
+ score,
858
+ found: true,
859
+ details: issues.length > 0 ? issues.slice(0, 3).join("; ") : "All tasks have acceptance criteria"
860
+ };
861
+ }
862
+ function scorePriorityDistribution(tasks) {
863
+ if (tasks.length === 0) {
864
+ return { name: "Priority Distribution", score: 0, found: false, details: "No tasks found" };
865
+ }
866
+ const priorities = tasks.map((t) => t.priority);
867
+ const uniquePriorities = new Set(priorities);
868
+ const issues = [];
869
+ let penalty = 0;
870
+ if (uniquePriorities.size === 1 && tasks.length > 2) {
871
+ penalty += 30;
872
+ issues.push(`All ${tasks.length} tasks have the same priority P${priorities[0]}`);
873
+ }
874
+ for (const task of tasks) {
875
+ if (task.priority === 1) {
876
+ for (const depId of task.dependsOn) {
877
+ const depTask = tasks.find((t) => t.id === depId);
878
+ if (depTask && depTask.priority > 2) {
879
+ penalty += 15;
880
+ issues.push(`P1 ${task.id} depends on P${depTask.priority} ${depId}`);
881
+ }
882
+ }
883
+ }
884
+ }
885
+ if (uniquePriorities.size >= 3 && tasks.length >= 4) {
886
+ penalty = Math.max(0, penalty - 10);
887
+ }
888
+ const score = Math.max(0, Math.round(100 - penalty));
889
+ return {
890
+ name: "Priority Distribution",
891
+ score,
892
+ found: true,
893
+ details: issues.length > 0 ? issues.slice(0, 3).join("; ") : `${uniquePriorities.size} priority levels across ${tasks.length} tasks`
894
+ };
895
+ }
896
+ function scoreCompleteness(doc) {
897
+ let score = 0;
898
+ const issues = [];
899
+ if (doc.description && doc.description !== "No description") {
900
+ score += 25;
901
+ } else {
902
+ issues.push("Missing overview/description");
903
+ }
904
+ if (doc.tasks.length >= 2) {
905
+ score += 25;
906
+ } else if (doc.tasks.length === 1) {
907
+ score += 15;
908
+ issues.push("Only 1 task found (consider splitting)");
909
+ } else {
910
+ issues.push("No tasks found");
911
+ }
912
+ const withDesc = doc.tasks.filter((t) => t.description && t.description !== t.title);
913
+ if (doc.tasks.length > 0 && withDesc.length === doc.tasks.length) {
914
+ score += 25;
915
+ } else if (withDesc.length > 0) {
916
+ score += Math.round(25 * (withDesc.length / Math.max(1, doc.tasks.length)));
917
+ issues.push(`${doc.tasks.length - withDesc.length} task(s) lack descriptions`);
918
+ }
919
+ if (doc.title && doc.title !== "Untitled PRD") {
920
+ score += 25;
921
+ } else {
922
+ issues.push("Missing or generic title");
923
+ }
924
+ return {
925
+ name: "Completeness",
926
+ score: Math.round(score),
927
+ found: true,
928
+ details: issues.length > 0 ? issues.join("; ") : "PRD has all required sections"
929
+ };
930
+ }
931
+ function scorePrdQuality(document) {
932
+ const dimensions = [
933
+ scoreTaskAtomicity(document.tasks),
934
+ scoreTaskDecoupling(document.tasks),
935
+ scoreDependencyClarity(document.tasks),
936
+ scoreTaskScope(document.tasks),
937
+ scoreParallelPotential(document.tasks),
938
+ scoreAcceptanceCriteria(document.tasks),
939
+ scorePriorityDistribution(document.tasks),
940
+ scoreCompleteness(document)
941
+ ];
942
+ const totalScore = dimensions.reduce((sum, d) => sum + d.score, 0);
943
+ const overall = Math.round(totalScore / dimensions.length);
944
+ const suggestions = [];
945
+ for (const dim of dimensions) {
946
+ if (dim.score < 50) {
947
+ switch (dim.name) {
948
+ case "Task Atomicity":
949
+ suggestions.push("Split tasks with more than 3 acceptance criteria into smaller, focused tasks.");
950
+ break;
951
+ case "Task Decoupling":
952
+ suggestions.push("Reduce file overlap between tasks by scoping each task to distinct areas of the codebase.");
953
+ break;
954
+ case "Dependency Clarity":
955
+ suggestions.push("Fix dangling dependency references and remove any circular dependencies.");
956
+ break;
957
+ case "Task Scope":
958
+ suggestions.push("Reference specific files, modules, or components in each task description.");
959
+ break;
960
+ case "Parallel Potential":
961
+ suggestions.push("Reduce unnecessary dependencies to enable more parallel execution.");
962
+ break;
963
+ case "Acceptance Criteria":
964
+ suggestions.push("Add 2+ acceptance criteria to every task for clear definition of done.");
965
+ break;
966
+ case "Priority Distribution":
967
+ suggestions.push("Use a range of priorities (P1-P4) and ensure P1 tasks do not depend on P3/P4 tasks.");
968
+ break;
969
+ case "Completeness":
970
+ suggestions.push("Add an overview section, meaningful title, and descriptions for all tasks.");
971
+ break;
972
+ }
973
+ }
974
+ }
975
+ return {
976
+ overall,
977
+ breakdown: dimensions,
978
+ suggestions,
979
+ scoredAt: Date.now(),
980
+ isAiScore: false
981
+ };
982
+ }
983
+ function buildAiPrdScoringPrompt(markdown) {
984
+ return `You are evaluating a Product Requirements Document (PRD) for quality.
985
+
986
+ ## PRD Content
987
+
988
+ ${markdown}
989
+
990
+ ## Evaluation Criteria
991
+
992
+ Score each dimension from 0-100:
993
+
994
+ 1. **Task Atomicity** - Are tasks small enough to complete in a single session? Do any have too many acceptance criteria?
995
+ 2. **Task Decoupling** - Can tasks be worked on independently? Do they reference overlapping files?
996
+ 3. **Dependency Clarity** - Are dependencies valid and acyclic? Do they accurately reflect build order?
997
+ 4. **Task Scope** - Do tasks reference specific files, modules, or components from the actual codebase?
998
+ 5. **Parallel Potential** - What percentage of tasks can run in parallel based on the dependency graph?
999
+ 6. **Acceptance Criteria** - Do all tasks have clear, testable acceptance criteria?
1000
+ 7. **Priority Distribution** - Is there a reasonable spread of priorities? Are high-priority tasks blocking lower-priority work?
1001
+ 8. **Completeness** - Does the PRD have an overview, title, descriptions for all tasks?
1002
+
1003
+ ## Codebase Analysis
1004
+
1005
+ Before scoring, explore the codebase to verify:
1006
+ - Referenced files actually exist
1007
+ - Dependencies match actual module relationships
1008
+ - Task scope aligns with codebase architecture
1009
+
1010
+ ## Output Format
1011
+
1012
+ Respond with a JSON object (no markdown fencing):
1013
+
1014
+ {
1015
+ "overall": <number 0-100>,
1016
+ "breakdown": [
1017
+ { "name": "<dimension name>", "score": <number 0-100>, "found": true, "details": "<explanation>" },
1018
+ ...
1019
+ ],
1020
+ "suggestions": ["<actionable improvement tip>", ...]
1021
+ }`;
1022
+ }
1023
+ function buildPrdEnhancementPrompt(existingMarkdown, feedback, options) {
1024
+ const parts = [];
1025
+ parts.push("## Instructions\n");
1026
+ parts.push(ENHANCEMENT_PROMPT);
1027
+ parts.push("");
1028
+ parts.push("## Existing PRD\n");
1029
+ parts.push("```markdown");
1030
+ parts.push(existingMarkdown);
1031
+ parts.push("```");
1032
+ parts.push("");
1033
+ if (feedback.annotations.length > 0) {
1034
+ parts.push("## User Annotations\n");
1035
+ const concerns = feedback.annotations.filter(
1036
+ (a) => a.type === "concern"
1037
+ );
1038
+ const suggestions = feedback.annotations.filter(
1039
+ (a) => a.type === "suggestion"
1040
+ );
1041
+ const questions = feedback.annotations.filter(
1042
+ (a) => a.type === "question"
1043
+ );
1044
+ const comments = feedback.annotations.filter(
1045
+ (a) => a.type === "comment" || a.type === "global_comment" || a.type === "approval"
1046
+ );
1047
+ const instructionAnns = feedback.annotations.filter(
1048
+ (a) => a.type === "instruction"
1049
+ );
1050
+ if (concerns.length > 0) {
1051
+ parts.push("### Concerns (address these first)\n");
1052
+ for (const a of concerns) {
1053
+ const loc = a.sectionTitle ? ` [Section: ${a.sectionTitle}]` : "";
1054
+ parts.push(`- **CONCERN**${loc}: ${a.text}`);
1055
+ }
1056
+ parts.push("");
1057
+ }
1058
+ if (suggestions.length > 0) {
1059
+ parts.push("### Suggestions\n");
1060
+ for (const a of suggestions) {
1061
+ const loc = a.sectionTitle ? ` [Section: ${a.sectionTitle}]` : "";
1062
+ parts.push(`- **SUGGESTION**${loc}: ${a.text}`);
1063
+ }
1064
+ parts.push("");
1065
+ }
1066
+ if (questions.length > 0) {
1067
+ parts.push("### Questions (answer these in the PRD)\n");
1068
+ for (const a of questions) {
1069
+ const loc = a.sectionTitle ? ` [Section: ${a.sectionTitle}]` : "";
1070
+ parts.push(`- **QUESTION**${loc}: ${a.text}`);
1071
+ }
1072
+ parts.push("");
1073
+ }
1074
+ if (instructionAnns.length > 0) {
1075
+ parts.push("### Annotation Instructions\n");
1076
+ for (const a of instructionAnns) {
1077
+ const loc = a.sectionTitle ? ` [Section: ${a.sectionTitle}]` : "";
1078
+ parts.push(`- **INSTRUCTION**${loc}: ${a.text}`);
1079
+ }
1080
+ parts.push("");
1081
+ }
1082
+ if (comments.length > 0) {
1083
+ parts.push("### Comments\n");
1084
+ for (const a of comments) {
1085
+ const loc = a.sectionTitle ? ` [Section: ${a.sectionTitle}]` : "";
1086
+ parts.push(`- ${a.type.toUpperCase()}${loc}: ${a.text}`);
1087
+ }
1088
+ parts.push("");
1089
+ }
1090
+ }
1091
+ if (feedback.edits.length > 0) {
1092
+ parts.push("## Inline Edits (apply these changes)\n");
1093
+ for (const edit of feedback.edits) {
1094
+ parts.push(`### Section: ${edit.sectionTitle}
1095
+ `);
1096
+ parts.push("**Original:**");
1097
+ parts.push("```");
1098
+ parts.push(edit.original.slice(0, 2e3));
1099
+ parts.push("```");
1100
+ parts.push("**Edited (use this version):**");
1101
+ parts.push("```");
1102
+ parts.push(edit.edited.slice(0, 2e3));
1103
+ parts.push("```");
1104
+ parts.push("");
1105
+ }
1106
+ }
1107
+ if (feedback.priorities.length > 0) {
1108
+ parts.push("## Priority Assignments\n");
1109
+ parts.push(
1110
+ "Apply these priority levels. Sections marked must-have should be expanded with more detail. Sections marked out-of-scope should be removed or moved to a 'Future Considerations' section.\n"
1111
+ );
1112
+ for (const p of feedback.priorities) {
1113
+ const note = p.note ? ` \u2014 ${p.note}` : "";
1114
+ parts.push(`- **${p.sectionTitle}**: ${p.level}${note}`);
1115
+ }
1116
+ parts.push("");
1117
+ }
1118
+ if (feedback.risks.length > 0) {
1119
+ parts.push("## Risk Flags\n");
1120
+ parts.push(
1121
+ "High-risk sections need mitigation steps added. Medium-risk sections should note the risk. Low-risk items are informational.\n"
1122
+ );
1123
+ for (const r of feedback.risks) {
1124
+ const desc = r.description ? ` \u2014 ${r.description}` : "";
1125
+ parts.push(`- **${r.sectionTitle}**: ${r.level} risk${desc}`);
1126
+ }
1127
+ parts.push("");
1128
+ }
1129
+ if (feedback.instructions.length > 0) {
1130
+ parts.push("## Explicit Instructions\n");
1131
+ parts.push("Follow these directives exactly:\n");
1132
+ for (const i of feedback.instructions) {
1133
+ const pri = i.priority ? ` [${i.priority}]` : "";
1134
+ parts.push(`- **${i.sectionTitle}**${pri}: ${i.instruction}`);
1135
+ }
1136
+ parts.push("");
1137
+ }
1138
+ const outputDir = options?.outputDir ?? ".ulpi/prds";
1139
+ const slug = options?.slug ?? "enhanced-prd";
1140
+ const outputPath = `${outputDir}/prd-${slug}.md`;
1141
+ parts.push("## Output\n");
1142
+ parts.push(`Write the enhanced PRD to: \`${outputPath}\``);
1143
+ parts.push("Use the Write tool to create the file at this path.");
1144
+ parts.push(
1145
+ "The enhanced PRD should address ALL the feedback above while maintaining the same overall structure."
1146
+ );
1147
+ return parts.join("\n");
1148
+ }
1149
+ var ENHANCEMENT_PROMPT = `You are an expert product engineer enhancing a Product Requirements Document (PRD).
1150
+
1151
+ ## Your Mission
1152
+
1153
+ You will receive:
1154
+ 1. An existing PRD markdown document
1155
+ 2. User feedback: annotations, inline edits, priority assignments, risk flags, and explicit instructions
1156
+
1157
+ Your job is to produce an **enhanced version** of the PRD that addresses ALL feedback while:
1158
+ - Maintaining the overall document structure and format
1159
+ - Preserving content that was NOT flagged for changes
1160
+ - Applying inline edits exactly as specified
1161
+ - Expanding sections marked as must-have priorities
1162
+ - Adding mitigation steps for high-risk sections
1163
+ - Answering questions raised in annotations directly in the PRD text
1164
+ - Addressing concerns by improving the relevant sections
1165
+ - Incorporating suggestions where they improve the document
1166
+
1167
+ ## Important Rules
1168
+
1169
+ - Work **autonomously** \u2014 do NOT ask questions or wait for interaction.
1170
+ - **Explore the codebase** if needed to provide more specific file references or technical details.
1171
+ - Keep the PRD format parseable: use \`# PRD: Title\`, \`## Section\`, \`### TASK-NNN: Title\` format.
1172
+ - Do NOT remove tasks unless explicitly marked as out-of-scope.
1173
+ - If a section has both edits and annotations, apply the edit first, then address annotations.
1174
+ - Write the complete enhanced PRD to the output path using the Write tool.`;
1175
+ function clampPriority(p) {
1176
+ return Math.max(0, Math.min(4, Math.round(p)));
1177
+ }
1178
+ function convertTask(task) {
1179
+ return {
1180
+ id: task.id,
1181
+ title: task.title,
1182
+ description: task.description,
1183
+ status: "open",
1184
+ priority: clampPriority(task.priority),
1185
+ labels: [...task.labels],
1186
+ dependsOn: [...task.dependsOn],
1187
+ metadata: {
1188
+ acceptanceCriteria: [...task.acceptanceCriteria],
1189
+ source: "prd"
1190
+ }
1191
+ };
1192
+ }
1193
+ function computeDependencyDepths(tasks) {
1194
+ const depths = /* @__PURE__ */ new Map();
1195
+ const taskIds = new Set(tasks.map((t) => t.id));
1196
+ function getDepth(taskId, visited) {
1197
+ if (depths.has(taskId)) {
1198
+ return depths.get(taskId);
1199
+ }
1200
+ if (visited.has(taskId)) {
1201
+ return 0;
1202
+ }
1203
+ visited.add(taskId);
1204
+ const task = tasks.find((t) => t.id === taskId);
1205
+ if (!task || task.dependsOn.length === 0) {
1206
+ depths.set(taskId, 0);
1207
+ return 0;
1208
+ }
1209
+ let maxDepth = 0;
1210
+ for (const depId of task.dependsOn) {
1211
+ if (taskIds.has(depId)) {
1212
+ const depDepth = getDepth(depId, visited);
1213
+ maxDepth = Math.max(maxDepth, depDepth + 1);
1214
+ }
1215
+ }
1216
+ depths.set(taskId, maxDepth);
1217
+ return maxDepth;
1218
+ }
1219
+ for (const task of tasks) {
1220
+ getDepth(task.id, /* @__PURE__ */ new Set());
1221
+ }
1222
+ return depths;
1223
+ }
1224
+ function convertPrdToTasks(prd, useDependencyPriority = false) {
1225
+ const trackerTasks = prd.tasks.map(convertTask);
1226
+ if (useDependencyPriority) {
1227
+ const depths = computeDependencyDepths(prd.tasks);
1228
+ for (const task of trackerTasks) {
1229
+ const depth = depths.get(task.id) ?? 0;
1230
+ task.priority = clampPriority(depth + 1);
1231
+ }
1232
+ }
1233
+ const validIds = new Set(trackerTasks.map((t) => t.id));
1234
+ for (const task of trackerTasks) {
1235
+ task.dependsOn = (task.dependsOn ?? []).filter((depId) => validIds.has(depId));
1236
+ }
1237
+ return trackerTasks;
1238
+ }
1239
+ function convertPrdToJson(prd) {
1240
+ return {
1241
+ name: prd.title,
1242
+ description: prd.description,
1243
+ tasks: prd.tasks.map((task) => ({
1244
+ id: task.id,
1245
+ title: task.title,
1246
+ description: task.description,
1247
+ acceptanceCriteria: task.acceptanceCriteria,
1248
+ priority: task.priority,
1249
+ labels: task.labels,
1250
+ dependsOn: task.dependsOn,
1251
+ status: "pending"
1252
+ })),
1253
+ metadata: {
1254
+ ...prd.metadata,
1255
+ convertedAt: (/* @__PURE__ */ new Date()).toISOString(),
1256
+ version: "1.0.0"
1257
+ }
1258
+ };
1259
+ }
1260
+ function buildPrdValidationPrompt(prdMarkdown, _taskSummaries) {
1261
+ const parts = [];
1262
+ parts.push("## Your Role");
1263
+ parts.push("");
1264
+ parts.push("You are a senior engineer performing a **post-implementation validation**.");
1265
+ parts.push("Multiple agents worked on different tasks in parallel. Their work has been merged into this branch.");
1266
+ parts.push("You do NOT care about what the agents did or didn't do. You only care about the current state of the code on this branch.");
1267
+ parts.push("");
1268
+ parts.push("## Instructions");
1269
+ parts.push("");
1270
+ parts.push("### Step 1: Run the build");
1271
+ parts.push("");
1272
+ parts.push("Run the project's build command first to see the current state.");
1273
+ parts.push("Note any build errors \u2014 you will fix them.");
1274
+ parts.push("");
1275
+ parts.push("### Step 2: Validate task by task");
1276
+ parts.push("");
1277
+ parts.push("Go through each task in the PRD below, one by one, in order.");
1278
+ parts.push("For each task:");
1279
+ parts.push("");
1280
+ parts.push("1. Read the task description and acceptance criteria in the PRD");
1281
+ parts.push("2. Check if the code for that task exists on the branch (read the relevant files)");
1282
+ parts.push("3. If the code is there and correct \u2014 move on to the next task");
1283
+ parts.push("4. If the code is missing, incomplete, or broken \u2014 **implement or fix it**");
1284
+ parts.push("");
1285
+ parts.push("Do not skip tasks. Do not assume anything. Read the actual files.");
1286
+ parts.push("");
1287
+ parts.push("### Step 3: Fix build errors");
1288
+ parts.push("");
1289
+ parts.push("After going through all tasks, run the build again.");
1290
+ parts.push("Fix any build errors, type errors, broken imports, or missing exports.");
1291
+ parts.push("Keep running the build until it passes cleanly.");
1292
+ parts.push("");
1293
+ parts.push("## PRD");
1294
+ parts.push("");
1295
+ parts.push(prdMarkdown);
1296
+ return parts.join("\n");
1297
+ }
1298
+ function extractAcceptanceCriteria(markdown) {
1299
+ const criteria = [];
1300
+ const lines = markdown.split("\n");
1301
+ let inAcSection = false;
1302
+ for (const line of lines) {
1303
+ const trimmed = line.trim();
1304
+ if (/^#{2,5}\s+Acceptance\s+Criteria/i.test(trimmed) || /^\*\*Acceptance\s+Criteria[:\*]*\*\*/i.test(trimmed) || /^Acceptance\s+Criteria:$/i.test(trimmed)) {
1305
+ inAcSection = true;
1306
+ continue;
1307
+ }
1308
+ if (inAcSection) {
1309
+ if (/^#{2,5}\s+/.test(trimmed) && !/Acceptance\s+Criteria/i.test(trimmed)) {
1310
+ inAcSection = false;
1311
+ continue;
1312
+ }
1313
+ if (/^\*\*(?!Acceptance)/.test(trimmed)) {
1314
+ inAcSection = false;
1315
+ continue;
1316
+ }
1317
+ if (trimmed === "---") {
1318
+ inAcSection = false;
1319
+ continue;
1320
+ }
1321
+ }
1322
+ if (inAcSection) {
1323
+ const checkMatch = trimmed.match(/^[-*]\s+\[[\sx]\]\s+(.+)$/);
1324
+ if (checkMatch?.[1]) {
1325
+ criteria.push(checkMatch[1].trim());
1326
+ continue;
1327
+ }
1328
+ const bulletMatch = trimmed.match(/^[-*]\s+(.+)$/);
1329
+ if (bulletMatch?.[1] && !bulletMatch[1].startsWith("[")) {
1330
+ criteria.push(bulletMatch[1].trim());
1331
+ continue;
1332
+ }
1333
+ const numberedMatch = trimmed.match(/^\d+\.\s+(.+)$/);
1334
+ if (numberedMatch?.[1]) {
1335
+ criteria.push(numberedMatch[1].trim());
1336
+ continue;
1337
+ }
1338
+ if (/^(?:Given|When|Then|And|But)\s+/i.test(trimmed)) {
1339
+ criteria.push(trimmed);
1340
+ continue;
1341
+ }
1342
+ }
1343
+ }
1344
+ if (criteria.length === 0) {
1345
+ for (const line of lines) {
1346
+ const acMatch = line.trim().match(/^[-*]\s+\[AC\]\s+(.+)$/i);
1347
+ if (acMatch?.[1]) {
1348
+ criteria.push(acMatch[1].trim());
1349
+ }
1350
+ }
1351
+ }
1352
+ return criteria;
1353
+ }
1354
+ function formatCriteriaAsChecklist(criteria) {
1355
+ return criteria.map((c) => `- [ ] ${c}`).join("\n");
1356
+ }
1357
+ function hasAcceptanceCriteria(text) {
1358
+ return extractAcceptanceCriteria(text).length > 0;
1359
+ }
1360
+ export {
1361
+ buildAiPrdScoringPrompt,
1362
+ buildConversationPrompt,
1363
+ buildPrdEnhancementPrompt,
1364
+ buildPrdGenerationPrompt,
1365
+ buildPrdValidationPrompt,
1366
+ convertPrdToJson,
1367
+ convertPrdToTasks,
1368
+ detectPrd,
1369
+ extractAcceptanceCriteria,
1370
+ extractCriteria,
1371
+ formatCriteriaAsChecklist,
1372
+ getPrdGenerationPrompt,
1373
+ getPrdSystemPrompt,
1374
+ hasAcceptanceCriteria,
1375
+ parsePrdMarkdown,
1376
+ processPrdResponse,
1377
+ renderPrdMarkdown,
1378
+ scorePrdQuality,
1379
+ slugify
1380
+ };