@gethmy/mcp 2.3.0 → 2.3.2

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 (38) hide show
  1. package/dist/cli.js +80 -23
  2. package/dist/index.js +80 -23
  3. package/dist/lib/active-learning.js +939 -787
  4. package/dist/lib/api-client.js +2527 -638
  5. package/dist/lib/auto-session.js +177 -196
  6. package/dist/lib/cli.js +34954 -128
  7. package/dist/lib/config.js +235 -201
  8. package/dist/lib/consolidation.js +374 -289
  9. package/dist/lib/context-assembly.js +1265 -838
  10. package/dist/lib/graph-expansion.js +139 -155
  11. package/dist/lib/http.js +1917 -130
  12. package/dist/lib/index.js +29525 -5
  13. package/dist/lib/lifecycle-maintenance.js +663 -79
  14. package/dist/lib/memory-cleanup.js +1316 -381
  15. package/dist/lib/onboard.js +2588 -32
  16. package/dist/lib/prompt-builder.js +438 -445
  17. package/dist/lib/remote.js +31733 -143
  18. package/dist/lib/server.js +29389 -3216
  19. package/dist/lib/skills.js +315 -132
  20. package/dist/lib/tui/agents.js +128 -107
  21. package/dist/lib/tui/docs.js +1590 -687
  22. package/dist/lib/tui/setup.js +5698 -804
  23. package/dist/lib/tui/theme.js +183 -86
  24. package/dist/lib/tui/writer.js +1149 -176
  25. package/package.json +2 -2
  26. package/src/api-client.ts +37 -1
  27. package/src/memory-cleanup.ts +92 -52
  28. package/src/server.ts +16 -1
  29. package/dist/lib/__tests__/active-learning.test.js +0 -386
  30. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  31. package/dist/lib/__tests__/auto-session.test.js +0 -661
  32. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  33. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  34. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  35. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  36. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  37. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  38. package/dist/lib/__tests__/prompt-builder.test.js +0 -418
@@ -1,11 +1,308 @@
1
- /**
2
- * Prompt Builder for Harmony MCP Server
3
- *
4
- * Generates AI-ready prompts from Harmony cards with role-based framing,
5
- * context extraction, and variant-specific instructions.
6
- */
7
- // Label name to category mapping
8
- const LABEL_CATEGORY_MAP = {
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
+
31
+ // src/prompt-builder.ts
32
+ var exports_prompt_builder = {};
33
+ __export(exports_prompt_builder, {
34
+ inferCategoryFromLabels: () => inferCategoryFromLabels,
35
+ getRoleFraming: () => getRoleFraming,
36
+ getAvailableVariants: () => getAvailableVariants,
37
+ getAvailableCategories: () => getAvailableCategories,
38
+ generatePrompt: () => generatePrompt
39
+ });
40
+ function inferCategoryFromLabels(labels) {
41
+ for (const label of labels) {
42
+ const normalizedName = label.name.toLowerCase().trim();
43
+ if (LABEL_CATEGORY_MAP[normalizedName]) {
44
+ return LABEL_CATEGORY_MAP[normalizedName];
45
+ }
46
+ for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
47
+ if (normalizedName.includes(key) || key.includes(normalizedName)) {
48
+ return category;
49
+ }
50
+ }
51
+ }
52
+ return "custom";
53
+ }
54
+ function getRoleFraming(category) {
55
+ return DEFAULT_ROLE_FRAMINGS[category];
56
+ }
57
+ function estimateTokens(text) {
58
+ return Math.ceil(text.length / 4);
59
+ }
60
+ function formatSubtasks(subtasks) {
61
+ if (subtasks.length === 0)
62
+ return "";
63
+ const completed = subtasks.filter((s) => s.completed).length;
64
+ const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
65
+ return `
66
+ ## Subtasks (${completed}/${subtasks.length} completed)
67
+ ${lines.join(`
68
+ `)}`;
69
+ }
70
+ function formatLabels(labels) {
71
+ if (labels.length === 0)
72
+ return "";
73
+ return `
74
+ **Labels:** ${labels.map((l) => l.name).join(", ")}`;
75
+ }
76
+ function formatLinkedCards(links) {
77
+ if (!links || links.length === 0)
78
+ return "";
79
+ const lines = links.map((link) => {
80
+ const prefix = link.direction === "outgoing" ? "->" : "<-";
81
+ return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
82
+ });
83
+ return `
84
+ ## Related Cards
85
+ ${lines.join(`
86
+ `)}`;
87
+ }
88
+ function generatePrompt(options) {
89
+ const {
90
+ card,
91
+ column,
92
+ variant,
93
+ customConstraints,
94
+ memories,
95
+ assembledContext,
96
+ assemblyId
97
+ } = options;
98
+ const contextOpts = {
99
+ includeTitle: true,
100
+ includeDescription: true,
101
+ includeLabels: true,
102
+ includeSubtasks: true,
103
+ includeActivity: false,
104
+ includeAssignee: true,
105
+ includeDueDate: true,
106
+ includePriority: true,
107
+ includeLinks: true,
108
+ includeColumn: true,
109
+ ...options.contextOptions
110
+ };
111
+ const labels = card.labels || [];
112
+ const subtasks = card.subtasks || [];
113
+ const links = card.links || [];
114
+ const category = inferCategoryFromLabels(labels);
115
+ const roleFraming = getRoleFraming(category);
116
+ const sections = [];
117
+ sections.push(`# Role: ${roleFraming.role}
118
+ `);
119
+ sections.push(roleFraming.perspective);
120
+ sections.push("");
121
+ sections.push(VARIANT_INSTRUCTIONS[variant]);
122
+ sections.push("");
123
+ sections.push(`# Task: ${card.title}`);
124
+ if (contextOpts.includeColumn && column) {
125
+ sections.push(`**Status:** ${column.name}`);
126
+ }
127
+ if (contextOpts.includePriority) {
128
+ sections.push(`**Priority:** ${card.priority}`);
129
+ }
130
+ if (contextOpts.includeDueDate && card.due_date) {
131
+ sections.push(`**Due:** ${card.due_date}`);
132
+ }
133
+ if (contextOpts.includeAssignee && card.assignee) {
134
+ sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
135
+ }
136
+ if (contextOpts.includeLabels && labels.length > 0) {
137
+ sections.push(formatLabels(labels));
138
+ }
139
+ if (contextOpts.includeDescription && card.description) {
140
+ sections.push(`
141
+ ## Description
142
+ ${card.description}`);
143
+ }
144
+ if (contextOpts.includeSubtasks && subtasks.length > 0) {
145
+ sections.push(formatSubtasks(subtasks));
146
+ }
147
+ if (contextOpts.includeLinks && links.length > 0) {
148
+ sections.push(formatLinkedCards(links));
149
+ }
150
+ sections.push(`
151
+ ## Focus Areas`);
152
+ roleFraming.focus.forEach((f) => {
153
+ sections.push(`- ${f}`);
154
+ });
155
+ sections.push(`- **Memory:** Store reusable knowledge via \`harmony_remember\`. Only store what a future agent couldn't easily discover from the code itself, applies beyond this specific card, and includes a "because" (not just what, but why).`);
156
+ sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
157
+ sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
158
+ sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
159
+ sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
160
+ sections.push(`
161
+ ## Suggested Outputs`);
162
+ roleFraming.outputSuggestions.forEach((s) => {
163
+ sections.push(`- ${s}`);
164
+ });
165
+ if (assembledContext) {
166
+ sections.push(`
167
+ ${assembledContext}`);
168
+ } else if (memories && memories.length > 0) {
169
+ sections.push(`
170
+ ## Relevant Memories`);
171
+ sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
172
+ for (const memory of memories) {
173
+ const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
174
+ sections.push(`
175
+ ### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
176
+ sections.push(memory.content);
177
+ }
178
+ }
179
+ const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
180
+ if (oneThingLine) {
181
+ sections.push(`
182
+ ## Recommended Next Step
183
+ ${oneThingLine}`);
184
+ }
185
+ if (variant === "execute") {
186
+ sections.push(`
187
+ ## Progress Tracking
188
+ Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
189
+ - After exploring the codebase and understanding requirements (~20%)
190
+ - When you start implementing changes (~50%)
191
+ - When you move to testing or verification (~80%)
192
+ - When done, before ending the session (100%)
193
+
194
+ Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
195
+ }
196
+ if (customConstraints) {
197
+ sections.push(`
198
+ ## Additional Instructions
199
+ ${customConstraints}`);
200
+ }
201
+ sections.push(`
202
+ ---
203
+ *Card #${card.short_id} | Generated for ${variant} mode*`);
204
+ const prompt = sections.join(`
205
+ `);
206
+ const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
207
+ return {
208
+ prompt,
209
+ variant,
210
+ category,
211
+ role: roleFraming.role,
212
+ contextSummary: {
213
+ hasDescription: !!card.description,
214
+ labelCount: labels.length,
215
+ subtaskCount: subtasks.length,
216
+ completedSubtasks: subtasks.filter((s) => s.completed).length,
217
+ linkedCardCount: links.length,
218
+ memoryCount
219
+ },
220
+ tokenEstimate: estimateTokens(prompt),
221
+ ...assemblyId && { assemblyId }
222
+ };
223
+ }
224
+ function extractSessionInsights(assembledContext) {
225
+ const result = {
226
+ lastSessionStatus: null,
227
+ lastSessionTask: null,
228
+ lastSessionProgress: null,
229
+ blockers: [],
230
+ procedureNextStep: null
231
+ };
232
+ const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
233
+ if (sessionMatches && sessionMatches.length > 0) {
234
+ const latest = sessionMatches[0];
235
+ if (/Completed work on/i.test(latest)) {
236
+ result.lastSessionStatus = "completed";
237
+ } else if (/Paused work on|status:\s*paused/i.test(latest)) {
238
+ result.lastSessionStatus = "paused";
239
+ }
240
+ const taskMatch = latest.match(/Final task:\s*(.+)/);
241
+ if (taskMatch)
242
+ result.lastSessionTask = taskMatch[1].trim();
243
+ const progressMatch = latest.match(/Progress:\s*(\d+)%/);
244
+ if (progressMatch)
245
+ result.lastSessionProgress = parseInt(progressMatch[1], 10);
246
+ }
247
+ const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
248
+ if (blockerMatches) {
249
+ result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
250
+ }
251
+ const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
252
+ if (stepMatches && stepMatches.length > 0) {
253
+ result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
254
+ }
255
+ return result;
256
+ }
257
+ function synthesizeOneThing(card, subtasks, links, assembledContext) {
258
+ if (card.done)
259
+ return null;
260
+ const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
261
+ if (blockers.length > 0) {
262
+ const blocker = blockers[0];
263
+ return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
264
+ }
265
+ const session = assembledContext ? extractSessionInsights(assembledContext) : null;
266
+ if (session?.blockers && session.blockers.length > 0) {
267
+ return `Resolve blocker: ${session.blockers[0]}`;
268
+ }
269
+ if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
270
+ const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
271
+ return `Resume previous session${progress}: "${session.lastSessionTask}".`;
272
+ }
273
+ if (subtasks.length > 0) {
274
+ const completed = subtasks.filter((s) => s.completed).length;
275
+ if (completed === subtasks.length) {
276
+ return "All subtasks completed. Review the work and mark the card as done.";
277
+ }
278
+ const nextSubtask = subtasks.find((s) => !s.completed);
279
+ if (nextSubtask) {
280
+ return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
281
+ }
282
+ }
283
+ if (session?.procedureNextStep) {
284
+ return `Follow procedure: ${session.procedureNextStep}`;
285
+ }
286
+ if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
287
+ return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
288
+ }
289
+ if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
290
+ return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
291
+ }
292
+ if (card.description) {
293
+ return "Analyze the description, identify the approach, and begin implementation.";
294
+ }
295
+ return null;
296
+ }
297
+ function getAvailableCategories() {
298
+ return ["bug", "feature", "design", "review", "onboarding", "epic", "custom"];
299
+ }
300
+ function getAvailableVariants() {
301
+ return ["analysis", "draft", "execute"];
302
+ }
303
+ var LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
304
+ var init_prompt_builder = __esm(() => {
305
+ LABEL_CATEGORY_MAP = {
9
306
  bug: "bug",
10
307
  fix: "bug",
11
308
  hotfix: "bug",
@@ -33,456 +330,152 @@ const LABEL_CATEGORY_MAP = {
33
330
  epic: "epic",
34
331
  initiative: "epic",
35
332
  project: "epic",
36
- milestone: "epic",
37
- };
38
- // Default role framings
39
- const DEFAULT_ROLE_FRAMINGS = {
333
+ milestone: "epic"
334
+ };
335
+ DEFAULT_ROLE_FRAMINGS = {
40
336
  bug: {
41
- category: "bug",
42
- role: "Senior QA Engineer and Software Developer",
43
- perspective: "You are investigating and fixing a bug report.",
44
- focus: [
45
- "Root cause analysis",
46
- "Steps to reproduce",
47
- "Impact assessment",
48
- "Fix implementation",
49
- "Regression prevention",
50
- "Test cases to prevent recurrence",
51
- ],
52
- outputSuggestions: [
53
- "Bug triage summary",
54
- "Root cause explanation",
55
- "Fix implementation plan",
56
- "Test cases",
57
- ],
337
+ category: "bug",
338
+ role: "Senior QA Engineer and Software Developer",
339
+ perspective: "You are investigating and fixing a bug report.",
340
+ focus: [
341
+ "Root cause analysis",
342
+ "Steps to reproduce",
343
+ "Impact assessment",
344
+ "Fix implementation",
345
+ "Regression prevention",
346
+ "Test cases to prevent recurrence"
347
+ ],
348
+ outputSuggestions: [
349
+ "Bug triage summary",
350
+ "Root cause explanation",
351
+ "Fix implementation plan",
352
+ "Test cases"
353
+ ]
58
354
  },
59
355
  feature: {
60
- category: "feature",
61
- role: "Product Engineer",
62
- perspective: "You are implementing a new feature or enhancement.",
63
- focus: [
64
- "User requirements",
65
- "Technical specification",
66
- "Implementation approach",
67
- "Edge cases",
68
- "Acceptance criteria",
69
- "Integration points",
70
- ],
71
- outputSuggestions: [
72
- "Technical specification",
73
- "Implementation tasks",
74
- "Acceptance criteria checklist",
75
- "API design",
76
- ],
356
+ category: "feature",
357
+ role: "Product Engineer",
358
+ perspective: "You are implementing a new feature or enhancement.",
359
+ focus: [
360
+ "User requirements",
361
+ "Technical specification",
362
+ "Implementation approach",
363
+ "Edge cases",
364
+ "Acceptance criteria",
365
+ "Integration points"
366
+ ],
367
+ outputSuggestions: [
368
+ "Technical specification",
369
+ "Implementation tasks",
370
+ "Acceptance criteria checklist",
371
+ "API design"
372
+ ]
77
373
  },
78
374
  design: {
79
- category: "design",
80
- role: "UX Designer and Frontend Developer",
81
- perspective: "You are designing and implementing a user interface.",
82
- focus: [
83
- "User experience flow",
84
- "Visual design consistency",
85
- "Accessibility (WCAG)",
86
- "Responsive behavior",
87
- "Component architecture",
88
- "Interaction patterns",
89
- ],
90
- outputSuggestions: [
91
- "User flow diagram",
92
- "Component specifications",
93
- "Accessibility checklist",
94
- "Responsive breakpoints",
95
- ],
375
+ category: "design",
376
+ role: "UX Designer and Frontend Developer",
377
+ perspective: "You are designing and implementing a user interface.",
378
+ focus: [
379
+ "User experience flow",
380
+ "Visual design consistency",
381
+ "Accessibility (WCAG)",
382
+ "Responsive behavior",
383
+ "Component architecture",
384
+ "Interaction patterns"
385
+ ],
386
+ outputSuggestions: [
387
+ "User flow diagram",
388
+ "Component specifications",
389
+ "Accessibility checklist",
390
+ "Responsive breakpoints"
391
+ ]
96
392
  },
97
393
  review: {
98
- category: "review",
99
- role: "Code Reviewer and Technical Lead",
100
- perspective: "You are reviewing code for quality, correctness, and maintainability.",
101
- focus: [
102
- "Code correctness",
103
- "Performance implications",
104
- "Security considerations",
105
- "Testing coverage",
106
- "Documentation",
107
- "Best practices adherence",
108
- ],
109
- outputSuggestions: [
110
- "Review checklist",
111
- "Suggested improvements",
112
- "Test scenarios",
113
- "Security audit",
114
- ],
394
+ category: "review",
395
+ role: "Code Reviewer and Technical Lead",
396
+ perspective: "You are reviewing code for quality, correctness, and maintainability.",
397
+ focus: [
398
+ "Code correctness",
399
+ "Performance implications",
400
+ "Security considerations",
401
+ "Testing coverage",
402
+ "Documentation",
403
+ "Best practices adherence"
404
+ ],
405
+ outputSuggestions: [
406
+ "Review checklist",
407
+ "Suggested improvements",
408
+ "Test scenarios",
409
+ "Security audit"
410
+ ]
115
411
  },
116
412
  onboarding: {
117
- category: "onboarding",
118
- role: "Technical Writer and Developer Advocate",
119
- perspective: "You are creating documentation or onboarding materials.",
120
- focus: [
121
- "Clear step-by-step instructions",
122
- "Prerequisites and setup",
123
- "Common pitfalls",
124
- "Examples and use cases",
125
- "Troubleshooting guide",
126
- "Related resources",
127
- ],
128
- outputSuggestions: [
129
- "Getting started guide",
130
- "Step-by-step tutorial",
131
- "FAQ section",
132
- "Troubleshooting guide",
133
- ],
413
+ category: "onboarding",
414
+ role: "Technical Writer and Developer Advocate",
415
+ perspective: "You are creating documentation or onboarding materials.",
416
+ focus: [
417
+ "Clear step-by-step instructions",
418
+ "Prerequisites and setup",
419
+ "Common pitfalls",
420
+ "Examples and use cases",
421
+ "Troubleshooting guide",
422
+ "Related resources"
423
+ ],
424
+ outputSuggestions: [
425
+ "Getting started guide",
426
+ "Step-by-step tutorial",
427
+ "FAQ section",
428
+ "Troubleshooting guide"
429
+ ]
134
430
  },
135
431
  epic: {
136
- category: "epic",
137
- role: "Technical Project Manager and Architect",
138
- perspective: "You are planning and coordinating a large initiative.",
139
- focus: [
140
- "Scope definition",
141
- "Task breakdown",
142
- "Dependencies",
143
- "Risk assessment",
144
- "Timeline considerations",
145
- "Success metrics",
146
- ],
147
- outputSuggestions: [
148
- "Epic breakdown into stories",
149
- "Dependency graph",
150
- "Risk mitigation plan",
151
- "Success criteria",
152
- ],
432
+ category: "epic",
433
+ role: "Technical Project Manager and Architect",
434
+ perspective: "You are planning and coordinating a large initiative.",
435
+ focus: [
436
+ "Scope definition",
437
+ "Task breakdown",
438
+ "Dependencies",
439
+ "Risk assessment",
440
+ "Timeline considerations",
441
+ "Success metrics"
442
+ ],
443
+ outputSuggestions: [
444
+ "Epic breakdown into stories",
445
+ "Dependency graph",
446
+ "Risk mitigation plan",
447
+ "Success criteria"
448
+ ]
153
449
  },
154
450
  custom: {
155
- category: "custom",
156
- role: "Software Engineer",
157
- perspective: "You are working on a software task.",
158
- focus: [
159
- "Understanding requirements",
160
- "Implementation approach",
161
- "Quality considerations",
162
- "Testing strategy",
163
- ],
164
- outputSuggestions: [
165
- "Implementation plan",
166
- "Technical notes",
167
- "Task checklist",
168
- ],
169
- },
170
- };
171
- // Variant-specific instructions
172
- const VARIANT_INSTRUCTIONS = {
451
+ category: "custom",
452
+ role: "Software Engineer",
453
+ perspective: "You are working on a software task.",
454
+ focus: [
455
+ "Understanding requirements",
456
+ "Implementation approach",
457
+ "Quality considerations",
458
+ "Testing strategy"
459
+ ],
460
+ outputSuggestions: [
461
+ "Implementation plan",
462
+ "Technical notes",
463
+ "Task checklist"
464
+ ]
465
+ }
466
+ };
467
+ VARIANT_INSTRUCTIONS = {
173
468
  analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
174
469
  draft: `DRAFT MODE: Create a detailed implementation plan with code structure, key decisions, and approach. Include pseudocode or skeleton code where helpful. This is for review before full implementation.`,
175
- execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`,
176
- };
177
- /**
178
- * Infer category from labels
179
- */
180
- export function inferCategoryFromLabels(labels) {
181
- for (const label of labels) {
182
- const normalizedName = label.name.toLowerCase().trim();
183
- if (LABEL_CATEGORY_MAP[normalizedName]) {
184
- return LABEL_CATEGORY_MAP[normalizedName];
185
- }
186
- for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
187
- if (normalizedName.includes(key) || key.includes(normalizedName)) {
188
- return category;
189
- }
190
- }
191
- }
192
- return "custom";
193
- }
194
- /**
195
- * Get role framing for a category
196
- */
197
- export function getRoleFraming(category) {
198
- return DEFAULT_ROLE_FRAMINGS[category];
199
- }
200
- /**
201
- * Estimate token count (rough approximation: 1 token per 4 chars)
202
- */
203
- function estimateTokens(text) {
204
- return Math.ceil(text.length / 4);
205
- }
206
- /**
207
- * Format subtasks for prompt
208
- */
209
- function formatSubtasks(subtasks) {
210
- if (subtasks.length === 0)
211
- return "";
212
- const completed = subtasks.filter((s) => s.completed).length;
213
- const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
214
- return `\n## Subtasks (${completed}/${subtasks.length} completed)\n${lines.join("\n")}`;
215
- }
216
- /**
217
- * Format labels for prompt
218
- */
219
- function formatLabels(labels) {
220
- if (labels.length === 0)
221
- return "";
222
- return `\n**Labels:** ${labels.map((l) => l.name).join(", ")}`;
223
- }
224
- /**
225
- * Format linked cards for prompt
226
- */
227
- function formatLinkedCards(links) {
228
- if (!links || links.length === 0)
229
- return "";
230
- const lines = links.map((link) => {
231
- const prefix = link.direction === "outgoing" ? "->" : "<-";
232
- return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
233
- });
234
- return `\n## Related Cards\n${lines.join("\n")}`;
235
- }
236
- /**
237
- * Generate a prompt from a card
238
- */
239
- export function generatePrompt(options) {
240
- const { card, column, variant, customConstraints, memories, assembledContext, assemblyId, } = options;
241
- // Merge context options with defaults
242
- const contextOpts = {
243
- includeTitle: true,
244
- includeDescription: true,
245
- includeLabels: true,
246
- includeSubtasks: true,
247
- includeActivity: false,
248
- includeAssignee: true,
249
- includeDueDate: true,
250
- includePriority: true,
251
- includeLinks: true,
252
- includeColumn: true,
253
- ...options.contextOptions,
254
- };
255
- const labels = card.labels || [];
256
- const subtasks = card.subtasks || [];
257
- const links = card.links || [];
258
- const category = inferCategoryFromLabels(labels);
259
- const roleFraming = getRoleFraming(category);
260
- // Build prompt sections
261
- const sections = [];
262
- // Role and perspective
263
- sections.push(`# Role: ${roleFraming.role}\n`);
264
- sections.push(roleFraming.perspective);
265
- sections.push("");
266
- // Variant instruction
267
- sections.push(VARIANT_INSTRUCTIONS[variant]);
268
- sections.push("");
269
- // Task header
270
- sections.push(`# Task: ${card.title}`);
271
- if (contextOpts.includeColumn && column) {
272
- sections.push(`**Status:** ${column.name}`);
273
- }
274
- if (contextOpts.includePriority) {
275
- sections.push(`**Priority:** ${card.priority}`);
276
- }
277
- if (contextOpts.includeDueDate && card.due_date) {
278
- sections.push(`**Due:** ${card.due_date}`);
279
- }
280
- if (contextOpts.includeAssignee && card.assignee) {
281
- sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
282
- }
283
- // Labels
284
- if (contextOpts.includeLabels && labels.length > 0) {
285
- sections.push(formatLabels(labels));
286
- }
287
- // Description
288
- if (contextOpts.includeDescription && card.description) {
289
- sections.push(`\n## Description\n${card.description}`);
290
- }
291
- // Subtasks
292
- if (contextOpts.includeSubtasks && subtasks.length > 0) {
293
- sections.push(formatSubtasks(subtasks));
294
- }
295
- // Linked cards
296
- if (contextOpts.includeLinks && links.length > 0) {
297
- sections.push(formatLinkedCards(links));
298
- }
299
- // Focus areas
300
- sections.push(`\n## Focus Areas`);
301
- roleFraming.focus.forEach((f) => {
302
- sections.push(`- ${f}`);
303
- });
304
- sections.push(`- **Memory:** Store reusable knowledge via \`harmony_remember\`. Only store what a future agent couldn't easily discover from the code itself, applies beyond this specific card, and includes a "because" (not just what, but why).`);
305
- sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
306
- sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
307
- sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
308
- sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
309
- // Output suggestions
310
- sections.push(`\n## Suggested Outputs`);
311
- roleFraming.outputSuggestions.forEach((s) => {
312
- sections.push(`- ${s}`);
313
- });
314
- // Relevant memories from knowledge graph
315
- if (assembledContext) {
316
- // Use pre-assembled context from context assembly engine
317
- sections.push(`\n${assembledContext}`);
318
- }
319
- else if (memories && memories.length > 0) {
320
- // Fallback: legacy memory format
321
- sections.push(`\n## Relevant Memories`);
322
- sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
323
- for (const memory of memories) {
324
- const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
325
- sections.push(`\n### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
326
- sections.push(memory.content);
327
- }
328
- }
329
- // "One Thing" synthesis — highest-leverage next action
330
- const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
331
- if (oneThingLine) {
332
- sections.push(`\n## Recommended Next Step\n${oneThingLine}`);
333
- }
334
- // Progress tracking (execute variant only)
335
- if (variant === "execute") {
336
- sections.push(`\n## Progress Tracking
337
- Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
338
- - After exploring the codebase and understanding requirements (~20%)
339
- - When you start implementing changes (~50%)
340
- - When you move to testing or verification (~80%)
341
- - When done, before ending the session (100%)
470
+ execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
471
+ };
472
+ });
473
+ init_prompt_builder();
342
474
 
343
- Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
344
- }
345
- // Custom constraints
346
- if (customConstraints) {
347
- sections.push(`\n## Additional Instructions\n${customConstraints}`);
348
- }
349
- // Card reference footer
350
- sections.push(`\n---\n*Card #${card.short_id} | Generated for ${variant} mode*`);
351
- const prompt = sections.join("\n");
352
- const memoryCount = assembledContext
353
- ? (assembledContext.match(/^### /gm) || []).length
354
- : memories?.length || 0;
355
- return {
356
- prompt,
357
- variant,
358
- category,
359
- role: roleFraming.role,
360
- contextSummary: {
361
- hasDescription: !!card.description,
362
- labelCount: labels.length,
363
- subtaskCount: subtasks.length,
364
- completedSubtasks: subtasks.filter((s) => s.completed).length,
365
- linkedCardCount: links.length,
366
- memoryCount,
367
- },
368
- tokenEstimate: estimateTokens(prompt),
369
- ...(assemblyId && { assemblyId }),
370
- };
371
- }
372
- /**
373
- * Extract session insights from assembled context string.
374
- * Parses session summaries, blockers, and progress data.
375
- */
376
- function extractSessionInsights(assembledContext) {
377
- const result = {
378
- lastSessionStatus: null,
379
- lastSessionTask: null,
380
- lastSessionProgress: null,
381
- blockers: [],
382
- procedureNextStep: null,
383
- };
384
- // Find the most recent session summary with status
385
- const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
386
- if (sessionMatches && sessionMatches.length > 0) {
387
- const latest = sessionMatches[0];
388
- if (/Completed work on/i.test(latest)) {
389
- result.lastSessionStatus = "completed";
390
- }
391
- else if (/Paused work on|status:\s*paused/i.test(latest)) {
392
- result.lastSessionStatus = "paused";
393
- }
394
- const taskMatch = latest.match(/Final task:\s*(.+)/);
395
- if (taskMatch)
396
- result.lastSessionTask = taskMatch[1].trim();
397
- const progressMatch = latest.match(/Progress:\s*(\d+)%/);
398
- if (progressMatch)
399
- result.lastSessionProgress = parseInt(progressMatch[1], 10);
400
- }
401
- // Extract blockers from context
402
- const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
403
- if (blockerMatches) {
404
- result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
405
- }
406
- // Extract next procedure step (first uncompleted step)
407
- const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
408
- if (stepMatches && stepMatches.length > 0) {
409
- result.procedureNextStep = stepMatches[0]
410
- .replace(/^\d+\.\s+/, "")
411
- .replace(/\s*\*\*\[key step\]\*\*.*$/, "")
412
- .trim();
413
- }
414
- return result;
415
- }
416
- /**
417
- * Synthesize the single highest-leverage next action from card state
418
- * and assembled context (session history, blockers, procedures).
419
- * Inspired by ArtemXTech's "One Thing" pattern in /recall.
420
- */
421
- function synthesizeOneThing(card, subtasks, links, assembledContext) {
422
- // Priority 1: Card is already done
423
- if (card.done)
424
- return null;
425
- // Priority 2: Blocked by another card (via links)
426
- const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
427
- if (blockers.length > 0) {
428
- const blocker = blockers[0];
429
- return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
430
- }
431
- // Extract session insights from assembled context
432
- const session = assembledContext
433
- ? extractSessionInsights(assembledContext)
434
- : null;
435
- // Priority 3: Blockers detected in session context
436
- if (session?.blockers && session.blockers.length > 0) {
437
- return `Resolve blocker: ${session.blockers[0]}`;
438
- }
439
- // Priority 4: Previous session was paused — resume where it left off
440
- if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
441
- const progress = session.lastSessionProgress
442
- ? ` (was ${session.lastSessionProgress}% complete)`
443
- : "";
444
- return `Resume previous session${progress}: "${session.lastSessionTask}".`;
445
- }
446
- // Priority 5: Has subtasks — find the first incomplete one
447
- if (subtasks.length > 0) {
448
- const completed = subtasks.filter((s) => s.completed).length;
449
- if (completed === subtasks.length) {
450
- return "All subtasks completed. Review the work and mark the card as done.";
451
- }
452
- const nextSubtask = subtasks.find((s) => !s.completed);
453
- if (nextSubtask) {
454
- return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
455
- }
456
- }
457
- // Priority 6: Procedure has a next step
458
- if (session?.procedureNextStep) {
459
- return `Follow procedure: ${session.procedureNextStep}`;
460
- }
461
- // Priority 7: Previous session completed — build on it
462
- if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
463
- return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
464
- }
465
- // Priority 8: High/urgent priority with due date
466
- if (card.due_date &&
467
- (card.priority === "urgent" || card.priority === "high")) {
468
- return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
469
- }
470
- // Priority 9: Has description — start working
471
- if (card.description) {
472
- return "Analyze the description, identify the approach, and begin implementation.";
473
- }
474
- // Fallback
475
- return null;
476
- }
477
- /**
478
- * Get all available categories
479
- */
480
- export function getAvailableCategories() {
481
- return ["bug", "feature", "design", "review", "onboarding", "epic", "custom"];
482
- }
483
- /**
484
- * Get all available variants
485
- */
486
- export function getAvailableVariants() {
487
- return ["analysis", "draft", "execute"];
488
- }
475
+ export {
476
+ inferCategoryFromLabels,
477
+ getRoleFraming,
478
+ getAvailableVariants,
479
+ getAvailableCategories,
480
+ generatePrompt
481
+ };