@gethmy/mcp 2.24.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +182 -74
- package/dist/index.js +182 -74
- package/dist/lib/api-client.js +59 -70
- package/package.json +1 -1
- package/src/api-client.ts +165 -5
- package/src/playbook-metric-warnings.ts +56 -0
- package/src/prompt-builder.ts +9 -120
- package/src/read-consumer.ts +16 -0
- package/src/server.ts +41 -11
package/src/prompt-builder.ts
CHANGED
|
@@ -402,8 +402,6 @@ export interface GeneratePromptOptions {
|
|
|
402
402
|
contextOptions?: Partial<PromptContextOptions>;
|
|
403
403
|
customConstraints?: string;
|
|
404
404
|
memories?: MemoryData[];
|
|
405
|
-
/** Pre-assembled context string from context assembly engine */
|
|
406
|
-
assembledContext?: string;
|
|
407
405
|
/** Assembly ID for manifest tracking */
|
|
408
406
|
assemblyId?: string;
|
|
409
407
|
}
|
|
@@ -414,15 +412,8 @@ export interface GeneratePromptOptions {
|
|
|
414
412
|
export function generatePrompt(
|
|
415
413
|
options: GeneratePromptOptions,
|
|
416
414
|
): GeneratedPrompt {
|
|
417
|
-
const {
|
|
418
|
-
|
|
419
|
-
column,
|
|
420
|
-
variant,
|
|
421
|
-
customConstraints,
|
|
422
|
-
memories,
|
|
423
|
-
assembledContext,
|
|
424
|
-
assemblyId,
|
|
425
|
-
} = options;
|
|
415
|
+
const { card, column, variant, customConstraints, memories, assemblyId } =
|
|
416
|
+
options;
|
|
426
417
|
|
|
427
418
|
// Merge context options with defaults
|
|
428
419
|
const contextOpts: PromptContextOptions = {
|
|
@@ -537,11 +528,7 @@ export function generatePrompt(
|
|
|
537
528
|
});
|
|
538
529
|
|
|
539
530
|
// Relevant memories from knowledge graph
|
|
540
|
-
if (
|
|
541
|
-
// Use pre-assembled context from context assembly engine
|
|
542
|
-
sections.push(`\n${assembledContext}`);
|
|
543
|
-
} else if (memories && memories.length > 0) {
|
|
544
|
-
// Fallback: legacy memory format
|
|
531
|
+
if (memories && memories.length > 0) {
|
|
545
532
|
sections.push(`\n## Relevant Memories`);
|
|
546
533
|
sections.push(
|
|
547
534
|
`*${memories.length} memories recalled from knowledge graph:*`,
|
|
@@ -556,12 +543,7 @@ export function generatePrompt(
|
|
|
556
543
|
}
|
|
557
544
|
|
|
558
545
|
// "One Thing" synthesis — highest-leverage next action
|
|
559
|
-
const oneThingLine = synthesizeOneThing(
|
|
560
|
-
card,
|
|
561
|
-
subtasks,
|
|
562
|
-
links,
|
|
563
|
-
assembledContext,
|
|
564
|
-
);
|
|
546
|
+
const oneThingLine = synthesizeOneThing(card, subtasks, links);
|
|
565
547
|
if (oneThingLine) {
|
|
566
548
|
sections.push(`\n## Recommended Next Step\n${oneThingLine}`);
|
|
567
549
|
}
|
|
@@ -590,9 +572,7 @@ Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working
|
|
|
590
572
|
|
|
591
573
|
const prompt = sections.join("\n");
|
|
592
574
|
|
|
593
|
-
const memoryCount =
|
|
594
|
-
? (assembledContext.match(/^### /gm) || []).length
|
|
595
|
-
: memories?.length || 0;
|
|
575
|
+
const memoryCount = memories?.length ?? 0;
|
|
596
576
|
|
|
597
577
|
return {
|
|
598
578
|
prompt,
|
|
@@ -616,69 +596,7 @@ Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working
|
|
|
616
596
|
}
|
|
617
597
|
|
|
618
598
|
/**
|
|
619
|
-
*
|
|
620
|
-
* Parses session summaries, blockers, and progress data.
|
|
621
|
-
*/
|
|
622
|
-
function extractSessionInsights(assembledContext: string): {
|
|
623
|
-
lastSessionStatus: "completed" | "paused" | null;
|
|
624
|
-
lastSessionTask: string | null;
|
|
625
|
-
lastSessionProgress: number | null;
|
|
626
|
-
blockers: string[];
|
|
627
|
-
procedureNextStep: string | null;
|
|
628
|
-
} {
|
|
629
|
-
const result = {
|
|
630
|
-
lastSessionStatus: null as "completed" | "paused" | null,
|
|
631
|
-
lastSessionTask: null as string | null,
|
|
632
|
-
lastSessionProgress: null as number | null,
|
|
633
|
-
blockers: [] as string[],
|
|
634
|
-
procedureNextStep: null as string | null,
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
// Find the most recent session summary with status
|
|
638
|
-
const sessionMatches = assembledContext.match(
|
|
639
|
-
/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g,
|
|
640
|
-
);
|
|
641
|
-
if (sessionMatches && sessionMatches.length > 0) {
|
|
642
|
-
const latest = sessionMatches[0];
|
|
643
|
-
if (/Completed work on/i.test(latest)) {
|
|
644
|
-
result.lastSessionStatus = "completed";
|
|
645
|
-
} else if (/Paused work on|status:\s*paused/i.test(latest)) {
|
|
646
|
-
result.lastSessionStatus = "paused";
|
|
647
|
-
}
|
|
648
|
-
const taskMatch = latest.match(/Final task:\s*(.+)/);
|
|
649
|
-
if (taskMatch) result.lastSessionTask = taskMatch[1].trim();
|
|
650
|
-
const progressMatch = latest.match(/Progress:\s*(\d+)%/);
|
|
651
|
-
if (progressMatch)
|
|
652
|
-
result.lastSessionProgress = parseInt(progressMatch[1], 10);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Extract blockers from context
|
|
656
|
-
const blockerMatches = assembledContext.match(
|
|
657
|
-
/(?:blocker|blocked by|blocking):\s*(.+)/gi,
|
|
658
|
-
);
|
|
659
|
-
if (blockerMatches) {
|
|
660
|
-
result.blockers = blockerMatches.map((m) =>
|
|
661
|
-
m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim(),
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// Extract next procedure step (first uncompleted step)
|
|
666
|
-
const stepMatches = assembledContext.match(
|
|
667
|
-
/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm,
|
|
668
|
-
);
|
|
669
|
-
if (stepMatches && stepMatches.length > 0) {
|
|
670
|
-
result.procedureNextStep = stepMatches[0]
|
|
671
|
-
.replace(/^\d+\.\s+/, "")
|
|
672
|
-
.replace(/\s*\*\*\[key step\]\*\*.*$/, "")
|
|
673
|
-
.trim();
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
return result;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Synthesize the single highest-leverage next action from card state
|
|
681
|
-
* and assembled context (session history, blockers, procedures).
|
|
599
|
+
* Synthesize the single highest-leverage next action from card state.
|
|
682
600
|
* Inspired by ArtemXTech's "One Thing" pattern in /recall.
|
|
683
601
|
*/
|
|
684
602
|
function synthesizeOneThing(
|
|
@@ -689,7 +607,6 @@ function synthesizeOneThing(
|
|
|
689
607
|
display_type: string;
|
|
690
608
|
direction: "outgoing" | "incoming";
|
|
691
609
|
}>,
|
|
692
|
-
assembledContext?: string,
|
|
693
610
|
): string | null {
|
|
694
611
|
// Priority 1: Card is already done
|
|
695
612
|
if (card.done) return null;
|
|
@@ -703,25 +620,7 @@ function synthesizeOneThing(
|
|
|
703
620
|
return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
|
|
704
621
|
}
|
|
705
622
|
|
|
706
|
-
//
|
|
707
|
-
const session = assembledContext
|
|
708
|
-
? extractSessionInsights(assembledContext)
|
|
709
|
-
: null;
|
|
710
|
-
|
|
711
|
-
// Priority 3: Blockers detected in session context
|
|
712
|
-
if (session?.blockers && session.blockers.length > 0) {
|
|
713
|
-
return `Resolve blocker: ${session.blockers[0]}`;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Priority 4: Previous session was paused — resume where it left off
|
|
717
|
-
if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
|
|
718
|
-
const progress = session.lastSessionProgress
|
|
719
|
-
? ` (was ${session.lastSessionProgress}% complete)`
|
|
720
|
-
: "";
|
|
721
|
-
return `Resume previous session${progress}: "${session.lastSessionTask}".`;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// Priority 5: Has subtasks — find the first incomplete one
|
|
623
|
+
// Priority 3: Has subtasks — find the first incomplete one
|
|
725
624
|
if (subtasks.length > 0) {
|
|
726
625
|
const completed = subtasks.filter((s) => s.completed).length;
|
|
727
626
|
if (completed === subtasks.length) {
|
|
@@ -733,17 +632,7 @@ function synthesizeOneThing(
|
|
|
733
632
|
}
|
|
734
633
|
}
|
|
735
634
|
|
|
736
|
-
// Priority
|
|
737
|
-
if (session?.procedureNextStep) {
|
|
738
|
-
return `Follow procedure: ${session.procedureNextStep}`;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Priority 7: Previous session completed — build on it
|
|
742
|
-
if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
|
|
743
|
-
return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Priority 8: High/urgent priority with due date
|
|
635
|
+
// Priority 4: High/urgent priority with due date
|
|
747
636
|
if (
|
|
748
637
|
card.due_date &&
|
|
749
638
|
(card.priority === "urgent" || card.priority === "high")
|
|
@@ -751,7 +640,7 @@ function synthesizeOneThing(
|
|
|
751
640
|
return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
|
|
752
641
|
}
|
|
753
642
|
|
|
754
|
-
// Priority
|
|
643
|
+
// Priority 5: Has description — start working
|
|
755
644
|
if (card.description) {
|
|
756
645
|
return "Analyze the description, identify the approach, and begin implementation.";
|
|
757
646
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Read-consumer labels for `record_entity_reads` (migration
|
|
2
|
+
// 20260828120000_knowledge_entity_reads.sql).
|
|
3
|
+
//
|
|
4
|
+
// Duplicated in `supabase/functions/_shared/read-consumer.ts` rather than
|
|
5
|
+
// shared via `packages/harmony-shared`: this package has no dependency on
|
|
6
|
+
// `@harmony/shared` (so it could not import the type from there), and
|
|
7
|
+
// `packages/mobile` DOES depend on `@harmony/shared` (so adding it there
|
|
8
|
+
// would drag mobile's typecheck CI into a change mobile never uses). Both
|
|
9
|
+
// copies must match the SQL `CHECK` constraint exactly — six values, no more.
|
|
10
|
+
export type ReadConsumer =
|
|
11
|
+
| "agent-prompt"
|
|
12
|
+
| "assistant"
|
|
13
|
+
| "mcp-tool"
|
|
14
|
+
| "analyze"
|
|
15
|
+
| "search"
|
|
16
|
+
| "browse";
|
package/src/server.ts
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
} from "./memory-session.js";
|
|
70
70
|
import { lintTags, normalizeTags } from "./memory-tags.js";
|
|
71
71
|
import { onboardNewUser } from "./onboard.js";
|
|
72
|
+
import { collectPlaybookMetricWarnings } from "./playbook-metric-warnings.js";
|
|
72
73
|
import { stripSkillPreamble } from "./skills.js";
|
|
73
74
|
|
|
74
75
|
// --- Signed-upload handshake (artifacts & card attachments) ---
|
|
@@ -1206,7 +1207,7 @@ export const TOOLS = {
|
|
|
1206
1207
|
},
|
|
1207
1208
|
harmony_classify_card: {
|
|
1208
1209
|
description:
|
|
1209
|
-
"
|
|
1210
|
+
"DEPRECATED — run sizing now happens at daemon pickup and is run-scoped, so nothing reads `model_tier`, `intent` or `complexity_score` any more; this tool still writes them, but only the type label has an effect. Prefer letting card creation apply the type label. Sets `intent` (plan/think/implement/review), `complexity_score` (0-10), `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the type label (feature/bug/idea). Idempotent; never touches the user-owned `model_override`.",
|
|
1210
1211
|
inputSchema: {
|
|
1211
1212
|
type: "object",
|
|
1212
1213
|
properties: {
|
|
@@ -4680,6 +4681,12 @@ async function handleToolCall(
|
|
|
4680
4681
|
: undefined,
|
|
4681
4682
|
include_superseded: includeSuperseded,
|
|
4682
4683
|
include_episodes: includeEpisodes,
|
|
4684
|
+
// No consumer here on purpose (fix round 1, task 4): this fetches
|
|
4685
|
+
// an over-fetched candidate pool (up to `fetchLimit`, itself
|
|
4686
|
+
// padded above `requestedLimit`/`topK`) for Park to rescore, not
|
|
4687
|
+
// what the tool actually returns. Recording it would overstate
|
|
4688
|
+
// access_count by up to fetchLimit/finalCount per call. What we
|
|
4689
|
+
// actually deliver is recorded below, after trimming.
|
|
4683
4690
|
},
|
|
4684
4691
|
);
|
|
4685
4692
|
entities = (searchResult.entities ?? []) as any[];
|
|
@@ -4741,19 +4748,19 @@ async function handleToolCall(
|
|
|
4741
4748
|
trimmed = fitToBudget(trimmed, budgetTokens);
|
|
4742
4749
|
}
|
|
4743
4750
|
|
|
4744
|
-
//
|
|
4745
|
-
//
|
|
4746
|
-
//
|
|
4747
|
-
//
|
|
4748
|
-
//
|
|
4749
|
-
//
|
|
4750
|
-
//
|
|
4751
|
+
// Read accounting (task 4, fix round 1): record only the entities
|
|
4752
|
+
// actually delivered to the caller — after trimming and the budget
|
|
4753
|
+
// pass, not the larger over-fetched candidate pool the search above
|
|
4754
|
+
// deliberately left unrecorded. Routed through batch-touch, which
|
|
4755
|
+
// dispatches to the same recordEntityReads/record_entity_reads path
|
|
4756
|
+
// the search chokepoint uses when a consumer is given. Non-blocking;
|
|
4757
|
+
// best-effort.
|
|
4751
4758
|
if (trimmed.length > 0) {
|
|
4752
4759
|
const touchIds = trimmed
|
|
4753
4760
|
.map(({ entity }: { entity: any }) => entity?.id)
|
|
4754
4761
|
.filter((id: unknown): id is string => typeof id === "string");
|
|
4755
4762
|
if (touchIds.length > 0) {
|
|
4756
|
-
client.batchTouchMemoryEntities(touchIds).catch(() => {});
|
|
4763
|
+
client.batchTouchMemoryEntities(touchIds, "mcp-tool").catch(() => {});
|
|
4757
4764
|
}
|
|
4758
4765
|
}
|
|
4759
4766
|
|
|
@@ -5370,7 +5377,18 @@ async function handleToolCall(
|
|
|
5370
5377
|
autoBind: args.autoBind,
|
|
5371
5378
|
catalogId: args.catalogId as string | undefined,
|
|
5372
5379
|
});
|
|
5373
|
-
|
|
5380
|
+
// #922: surface custom-gate metrics no agent declares — non-blocking,
|
|
5381
|
+
// the playbook is already created.
|
|
5382
|
+
const warnings = await collectPlaybookMetricWarnings(
|
|
5383
|
+
client,
|
|
5384
|
+
workspaceId,
|
|
5385
|
+
args.steps,
|
|
5386
|
+
);
|
|
5387
|
+
return {
|
|
5388
|
+
success: true,
|
|
5389
|
+
playbook: result.playbook,
|
|
5390
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
5391
|
+
};
|
|
5374
5392
|
}
|
|
5375
5393
|
|
|
5376
5394
|
case "harmony_update_playbook": {
|
|
@@ -5386,7 +5404,19 @@ async function handleToolCall(
|
|
|
5386
5404
|
triggerType: args.triggerType as string | undefined,
|
|
5387
5405
|
...("autoBind" in args ? { autoBind: args.autoBind } : {}),
|
|
5388
5406
|
});
|
|
5389
|
-
|
|
5407
|
+
// #922: same non-blocking metric check as create. The playbook's own
|
|
5408
|
+
// workspace scopes the lookup — the active context may point elsewhere.
|
|
5409
|
+
const warnings = await collectPlaybookMetricWarnings(
|
|
5410
|
+
client,
|
|
5411
|
+
(result.playbook as { workspace_id?: string } | undefined)
|
|
5412
|
+
?.workspace_id,
|
|
5413
|
+
args.steps,
|
|
5414
|
+
);
|
|
5415
|
+
return {
|
|
5416
|
+
success: true,
|
|
5417
|
+
playbook: result.playbook,
|
|
5418
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
5419
|
+
};
|
|
5390
5420
|
}
|
|
5391
5421
|
|
|
5392
5422
|
// Deprecated (#612) — see harmony_run_playbook above.
|