@gethmy/mcp 2.9.1 → 2.9.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.
package/dist/cli.js CHANGED
@@ -20,7 +20,6 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
  // src/prompt-builder.ts
21
21
  var exports_prompt_builder = {};
22
22
  __export(exports_prompt_builder, {
23
- proposePromptVariant: () => proposePromptVariant,
24
23
  inferCategoryFromLabels: () => inferCategoryFromLabels,
25
24
  getRoleFraming: () => getRoleFraming,
26
25
  getAvailableVariants: () => getAvailableVariants,
@@ -337,26 +336,7 @@ function getAvailableCategories() {
337
336
  function getAvailableVariants() {
338
337
  return ["analysis", "draft", "execute"];
339
338
  }
340
- async function proposePromptVariant(contentHash, fetchCohort) {
341
- if (!contentHash)
342
- return null;
343
- const cohort = await fetchCohort(contentHash);
344
- if (!cohort || cohort.length < VARIANT_MIN_COHORT)
345
- return null;
346
- const completed = cohort.filter((r) => r.status === "completed" && (r.progressPercent ?? 0) >= 100 && !r.hadBlockers).length;
347
- const completionRate = completed / cohort.length;
348
- if (completionRate >= VARIANT_COMPLETION_THRESHOLD)
349
- return null;
350
- const blockerRate = cohort.filter((r) => r.hadBlockers).length / cohort.length;
351
- const framingHint = blockerRate >= 0.4 ? "Cohort hits frequent blockers — try a more diagnostic framing (require root-cause + repro before any fix)." : "Cohort frequently stalls without finishing — try a more action-forcing framing (smaller subtasks, explicit DoD checklist).";
352
- return {
353
- contentHash,
354
- cohortSize: cohort.length,
355
- completionRate,
356
- framingHint
357
- };
358
- }
359
- var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS, VARIANT_MIN_COHORT = 10, VARIANT_COMPLETION_THRESHOLD = 0.4;
339
+ var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
360
340
  var init_prompt_builder = __esm(() => {
361
341
  LABEL_CATEGORY_MAP = {
362
342
  bug: "bug",
@@ -1860,16 +1840,6 @@ class HarmonyApiClient {
1860
1840
  async recordPromptHistory(data) {
1861
1841
  return this.request("POST", "/prompt-history", data);
1862
1842
  }
1863
- async recordPromptHistoryFeedback(sessionId, outcome) {
1864
- return this.request("POST", "/prompt-history/feedback", {
1865
- sessionId,
1866
- outcome
1867
- });
1868
- }
1869
- async getPromptHistoryCohort(contentHash) {
1870
- const params = new URLSearchParams({ content_hash: contentHash });
1871
- return this.request("GET", `/prompt-history/cohort?${params.toString()}`);
1872
- }
1873
1843
  async generateCardPrompt(options) {
1874
1844
  const { generatePrompt: generatePrompt2 } = await loadPromptModules();
1875
1845
  const cardResult = await this.getCard(options.cardId);
@@ -1974,6 +1944,23 @@ ${section}`;
1974
1944
  const msg = err instanceof Error ? err.message : String(err);
1975
1945
  console.debug(`[generateCardPrompt] comments fetch failed: ${msg}`);
1976
1946
  }
1947
+ if (cardData.plan_id) {
1948
+ try {
1949
+ const { plan } = await this.getPlan(cardData.plan_id);
1950
+ const planContent = plan?.content;
1951
+ if (planContent?.trim()) {
1952
+ result.prompt = `${result.prompt}
1953
+
1954
+ ## Approved Plan
1955
+ This card has an approved implementation plan. Follow it unless you find a concrete reason it is wrong — if you must diverge, say so in a comment and explain why.
1956
+
1957
+ ${planContent.trim()}`;
1958
+ }
1959
+ } catch (err) {
1960
+ const msg = err instanceof Error ? err.message : String(err);
1961
+ console.debug(`[generateCardPrompt] plan fetch failed: ${msg}`);
1962
+ }
1963
+ }
1977
1964
  try {
1978
1965
  await this.recordPromptHistory({
1979
1966
  cardId: cardData.id,
@@ -1983,11 +1970,7 @@ ${section}`;
1983
1970
  assemblyId: result.assemblyId ?? null,
1984
1971
  tokenEstimate: result.tokenEstimate,
1985
1972
  contextSummary: result.contextSummary
1986
- },
1987
- sessionId: options.sessionId ?? null,
1988
- contentHash: result.contentHash,
1989
- templateVersion: result.version,
1990
- confidence: 0.5
1973
+ }
1991
1974
  });
1992
1975
  } catch (err) {
1993
1976
  const msg = err instanceof Error ? err.message : String(err);
@@ -3200,7 +3183,7 @@ var TOOLS = {
3200
3183
  title: { type: "string", description: "Card title" },
3201
3184
  columnId: {
3202
3185
  type: "string",
3203
- description: "Target column ID (optional, defaults to first column)"
3186
+ description: "Target column ID (optional, defaults to the project's default column, or the first column if none is set)"
3204
3187
  },
3205
3188
  projectId: {
3206
3189
  type: "string",
@@ -5720,10 +5703,12 @@ async function handleToolCall(name, args, deps) {
5720
5703
  source: args.source || "agent",
5721
5704
  tasks: args.tasks
5722
5705
  });
5723
- const planUrl = `https://app.gethmy.com/plans/${result.plan.id}`;
5706
+ const planId = result.plan.id;
5707
+ const { workspaceSlug, projectSlug } = result;
5708
+ const planUrl = workspaceSlug && projectSlug ? `https://app.gethmy.com/${workspaceSlug}/${projectSlug}/plans/${planId}` : `https://app.gethmy.com/plans/${planId}`;
5724
5709
  return {
5725
5710
  success: true,
5726
- planId: result.plan.id,
5711
+ planId,
5727
5712
  planUrl,
5728
5713
  plan: result.plan,
5729
5714
  tasks: result.tasks
package/dist/index.js CHANGED
@@ -20,7 +20,6 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
  // src/prompt-builder.ts
21
21
  var exports_prompt_builder = {};
22
22
  __export(exports_prompt_builder, {
23
- proposePromptVariant: () => proposePromptVariant,
24
23
  inferCategoryFromLabels: () => inferCategoryFromLabels,
25
24
  getRoleFraming: () => getRoleFraming,
26
25
  getAvailableVariants: () => getAvailableVariants,
@@ -337,26 +336,7 @@ function getAvailableCategories() {
337
336
  function getAvailableVariants() {
338
337
  return ["analysis", "draft", "execute"];
339
338
  }
340
- async function proposePromptVariant(contentHash, fetchCohort) {
341
- if (!contentHash)
342
- return null;
343
- const cohort = await fetchCohort(contentHash);
344
- if (!cohort || cohort.length < VARIANT_MIN_COHORT)
345
- return null;
346
- const completed = cohort.filter((r) => r.status === "completed" && (r.progressPercent ?? 0) >= 100 && !r.hadBlockers).length;
347
- const completionRate = completed / cohort.length;
348
- if (completionRate >= VARIANT_COMPLETION_THRESHOLD)
349
- return null;
350
- const blockerRate = cohort.filter((r) => r.hadBlockers).length / cohort.length;
351
- const framingHint = blockerRate >= 0.4 ? "Cohort hits frequent blockers — try a more diagnostic framing (require root-cause + repro before any fix)." : "Cohort frequently stalls without finishing — try a more action-forcing framing (smaller subtasks, explicit DoD checklist).";
352
- return {
353
- contentHash,
354
- cohortSize: cohort.length,
355
- completionRate,
356
- framingHint
357
- };
358
- }
359
- var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS, VARIANT_MIN_COHORT = 10, VARIANT_COMPLETION_THRESHOLD = 0.4;
339
+ var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
360
340
  var init_prompt_builder = __esm(() => {
361
341
  LABEL_CATEGORY_MAP = {
362
342
  bug: "bug",
@@ -1856,16 +1836,6 @@ class HarmonyApiClient {
1856
1836
  async recordPromptHistory(data) {
1857
1837
  return this.request("POST", "/prompt-history", data);
1858
1838
  }
1859
- async recordPromptHistoryFeedback(sessionId, outcome) {
1860
- return this.request("POST", "/prompt-history/feedback", {
1861
- sessionId,
1862
- outcome
1863
- });
1864
- }
1865
- async getPromptHistoryCohort(contentHash) {
1866
- const params = new URLSearchParams({ content_hash: contentHash });
1867
- return this.request("GET", `/prompt-history/cohort?${params.toString()}`);
1868
- }
1869
1839
  async generateCardPrompt(options) {
1870
1840
  const { generatePrompt: generatePrompt2 } = await loadPromptModules();
1871
1841
  const cardResult = await this.getCard(options.cardId);
@@ -1970,6 +1940,23 @@ ${section}`;
1970
1940
  const msg = err instanceof Error ? err.message : String(err);
1971
1941
  console.debug(`[generateCardPrompt] comments fetch failed: ${msg}`);
1972
1942
  }
1943
+ if (cardData.plan_id) {
1944
+ try {
1945
+ const { plan } = await this.getPlan(cardData.plan_id);
1946
+ const planContent = plan?.content;
1947
+ if (planContent?.trim()) {
1948
+ result.prompt = `${result.prompt}
1949
+
1950
+ ## Approved Plan
1951
+ This card has an approved implementation plan. Follow it unless you find a concrete reason it is wrong — if you must diverge, say so in a comment and explain why.
1952
+
1953
+ ${planContent.trim()}`;
1954
+ }
1955
+ } catch (err) {
1956
+ const msg = err instanceof Error ? err.message : String(err);
1957
+ console.debug(`[generateCardPrompt] plan fetch failed: ${msg}`);
1958
+ }
1959
+ }
1973
1960
  try {
1974
1961
  await this.recordPromptHistory({
1975
1962
  cardId: cardData.id,
@@ -1979,11 +1966,7 @@ ${section}`;
1979
1966
  assemblyId: result.assemblyId ?? null,
1980
1967
  tokenEstimate: result.tokenEstimate,
1981
1968
  contextSummary: result.contextSummary
1982
- },
1983
- sessionId: options.sessionId ?? null,
1984
- contentHash: result.contentHash,
1985
- templateVersion: result.version,
1986
- confidence: 0.5
1969
+ }
1987
1970
  });
1988
1971
  } catch (err) {
1989
1972
  const msg = err instanceof Error ? err.message : String(err);
@@ -3196,7 +3179,7 @@ var TOOLS = {
3196
3179
  title: { type: "string", description: "Card title" },
3197
3180
  columnId: {
3198
3181
  type: "string",
3199
- description: "Target column ID (optional, defaults to first column)"
3182
+ description: "Target column ID (optional, defaults to the project's default column, or the first column if none is set)"
3200
3183
  },
3201
3184
  projectId: {
3202
3185
  type: "string",
@@ -5716,10 +5699,12 @@ async function handleToolCall(name, args, deps) {
5716
5699
  source: args.source || "agent",
5717
5700
  tasks: args.tasks
5718
5701
  });
5719
- const planUrl = `https://app.gethmy.com/plans/${result.plan.id}`;
5702
+ const planId = result.plan.id;
5703
+ const { workspaceSlug, projectSlug } = result;
5704
+ const planUrl = workspaceSlug && projectSlug ? `https://app.gethmy.com/${workspaceSlug}/${projectSlug}/plans/${planId}` : `https://app.gethmy.com/plans/${planId}`;
5720
5705
  return {
5721
5706
  success: true,
5722
- planId: result.plan.id,
5707
+ planId,
5723
5708
  planUrl,
5724
5709
  plan: result.plan,
5725
5710
  tasks: result.tasks
@@ -17,7 +17,6 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  // src/prompt-builder.ts
18
18
  var exports_prompt_builder = {};
19
19
  __export(exports_prompt_builder, {
20
- proposePromptVariant: () => proposePromptVariant,
21
20
  inferCategoryFromLabels: () => inferCategoryFromLabels,
22
21
  getRoleFraming: () => getRoleFraming,
23
22
  getAvailableVariants: () => getAvailableVariants,
@@ -334,26 +333,7 @@ function getAvailableCategories() {
334
333
  function getAvailableVariants() {
335
334
  return ["analysis", "draft", "execute"];
336
335
  }
337
- async function proposePromptVariant(contentHash, fetchCohort) {
338
- if (!contentHash)
339
- return null;
340
- const cohort = await fetchCohort(contentHash);
341
- if (!cohort || cohort.length < VARIANT_MIN_COHORT)
342
- return null;
343
- const completed = cohort.filter((r) => r.status === "completed" && (r.progressPercent ?? 0) >= 100 && !r.hadBlockers).length;
344
- const completionRate = completed / cohort.length;
345
- if (completionRate >= VARIANT_COMPLETION_THRESHOLD)
346
- return null;
347
- const blockerRate = cohort.filter((r) => r.hadBlockers).length / cohort.length;
348
- const framingHint = blockerRate >= 0.4 ? "Cohort hits frequent blockers — try a more diagnostic framing (require root-cause + repro before any fix)." : "Cohort frequently stalls without finishing — try a more action-forcing framing (smaller subtasks, explicit DoD checklist).";
349
- return {
350
- contentHash,
351
- cohortSize: cohort.length,
352
- completionRate,
353
- framingHint
354
- };
355
- }
356
- var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS, VARIANT_MIN_COHORT = 10, VARIANT_COMPLETION_THRESHOLD = 0.4;
336
+ var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
357
337
  var init_prompt_builder = __esm(() => {
358
338
  LABEL_CATEGORY_MAP = {
359
339
  bug: "bug",
@@ -1463,16 +1443,6 @@ class HarmonyApiClient {
1463
1443
  async recordPromptHistory(data) {
1464
1444
  return this.request("POST", "/prompt-history", data);
1465
1445
  }
1466
- async recordPromptHistoryFeedback(sessionId, outcome) {
1467
- return this.request("POST", "/prompt-history/feedback", {
1468
- sessionId,
1469
- outcome
1470
- });
1471
- }
1472
- async getPromptHistoryCohort(contentHash) {
1473
- const params = new URLSearchParams({ content_hash: contentHash });
1474
- return this.request("GET", `/prompt-history/cohort?${params.toString()}`);
1475
- }
1476
1446
  async generateCardPrompt(options) {
1477
1447
  const { generatePrompt: generatePrompt2 } = await loadPromptModules();
1478
1448
  const cardResult = await this.getCard(options.cardId);
@@ -1577,6 +1547,23 @@ ${section}`;
1577
1547
  const msg = err instanceof Error ? err.message : String(err);
1578
1548
  console.debug(`[generateCardPrompt] comments fetch failed: ${msg}`);
1579
1549
  }
1550
+ if (cardData.plan_id) {
1551
+ try {
1552
+ const { plan } = await this.getPlan(cardData.plan_id);
1553
+ const planContent = plan?.content;
1554
+ if (planContent?.trim()) {
1555
+ result.prompt = `${result.prompt}
1556
+
1557
+ ## Approved Plan
1558
+ This card has an approved implementation plan. Follow it unless you find a concrete reason it is wrong — if you must diverge, say so in a comment and explain why.
1559
+
1560
+ ${planContent.trim()}`;
1561
+ }
1562
+ } catch (err) {
1563
+ const msg = err instanceof Error ? err.message : String(err);
1564
+ console.debug(`[generateCardPrompt] plan fetch failed: ${msg}`);
1565
+ }
1566
+ }
1580
1567
  try {
1581
1568
  await this.recordPromptHistory({
1582
1569
  cardId: cardData.id,
@@ -1586,11 +1573,7 @@ ${section}`;
1586
1573
  assemblyId: result.assemblyId ?? null,
1587
1574
  tokenEstimate: result.tokenEstimate,
1588
1575
  contextSummary: result.contextSummary
1589
- },
1590
- sessionId: options.sessionId ?? null,
1591
- contentHash: result.contentHash,
1592
- templateVersion: result.version,
1593
- confidence: 0.5
1576
+ }
1594
1577
  });
1595
1578
  } catch (err) {
1596
1579
  const msg = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.9.1",
3
+ "version": "2.9.2",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/api-client.ts CHANGED
@@ -486,10 +486,12 @@ export class HarmonyApiClient {
486
486
  description?: string;
487
487
  priority?: string;
488
488
  assigneeId?: string | null;
489
+ assignedAgentId?: string | null;
489
490
  dueDate?: string | null;
490
491
  done?: boolean;
491
492
  archivedAt?: string | null;
492
493
  planId?: string | null;
494
+ needsPlanRefresh?: boolean;
493
495
  },
494
496
  ): Promise<{ card: unknown }> {
495
497
  return this.request("PATCH", `/cards/${cardId}`, updates);
@@ -734,6 +736,10 @@ export class HarmonyApiClient {
734
736
  | "budget"
735
737
  | "timeout"
736
738
  | "stale"
739
+ | "rate_limit"
740
+ | "out_of_credits"
741
+ | "usage_limit"
742
+ | "auth"
737
743
  | "other";
738
744
  failureSummary?: string;
739
745
  recoveryBranch?: string;
@@ -762,6 +768,10 @@ export class HarmonyApiClient {
762
768
  | "budget"
763
769
  | "timeout"
764
770
  | "stale"
771
+ | "rate_limit"
772
+ | "out_of_credits"
773
+ | "usage_limit"
774
+ | "auth"
765
775
  | "other";
766
776
  failureSummary?: string;
767
777
  recoveryBranch?: string;
@@ -1215,7 +1225,12 @@ export class HarmonyApiClient {
1215
1225
  status?: "pending" | "in_progress" | "completed";
1216
1226
  }>;
1217
1227
  },
1218
- ): Promise<{ plan: unknown; tasks?: unknown[] }> {
1228
+ ): Promise<{
1229
+ plan: unknown;
1230
+ tasks?: unknown[];
1231
+ projectSlug?: string | null;
1232
+ workspaceSlug?: string | null;
1233
+ }> {
1219
1234
  return this.request("POST", "/plans", { projectId, ...data });
1220
1235
  }
1221
1236
 
@@ -1322,37 +1337,12 @@ export class HarmonyApiClient {
1322
1337
  generatedPrompt: string;
1323
1338
  variant: "analysis" | "draft" | "execute";
1324
1339
  contextIncluded?: Record<string, unknown>;
1325
- sessionId?: string | null;
1326
- contentHash?: string;
1327
- templateVersion?: number;
1328
- confidence?: number;
1329
1340
  templateId?: string | null;
1330
1341
  isPinned?: boolean;
1331
1342
  }): Promise<{ entry: unknown }> {
1332
1343
  return this.request("POST", "/prompt-history", data);
1333
1344
  }
1334
1345
 
1335
- async recordPromptHistoryFeedback(
1336
- sessionId: string,
1337
- outcome: "success" | "blocker" | "neutral",
1338
- ): Promise<{ adjusted: number }> {
1339
- return this.request("POST", "/prompt-history/feedback", {
1340
- sessionId,
1341
- outcome,
1342
- });
1343
- }
1344
-
1345
- async getPromptHistoryCohort(contentHash: string): Promise<{
1346
- cohort: Array<{
1347
- status: string | null;
1348
- progressPercent: number | null;
1349
- hadBlockers: boolean;
1350
- }>;
1351
- }> {
1352
- const params = new URLSearchParams({ content_hash: contentHash });
1353
- return this.request("GET", `/prompt-history/cohort?${params.toString()}`);
1354
- }
1355
-
1356
1346
  // ============ PROMPT GENERATION ============
1357
1347
 
1358
1348
  /**
@@ -1373,8 +1363,6 @@ export class HarmonyApiClient {
1373
1363
  includeLinks: boolean;
1374
1364
  includeDescription: boolean;
1375
1365
  }>;
1376
- /** Optional active session ID to associate with the prompt snapshot. */
1377
- sessionId?: string | null;
1378
1366
  }): Promise<{
1379
1367
  prompt: string;
1380
1368
  variant: string;
@@ -1546,8 +1534,30 @@ export class HarmonyApiClient {
1546
1534
  console.debug(`[generateCardPrompt] comments fetch failed: ${msg}`);
1547
1535
  }
1548
1536
 
1549
- // AGP P2: persist a session-linked snapshot. Best-effort never fail
1550
- // prompt generation just because logging didn't land.
1537
+ // Surface a linked plan's full markdown (e.g. a plan the agent daemon
1538
+ // produced and a human approved in gated mode, or any plan attached via
1539
+ // cards.plan_id). This is the central injection point so EVERY caller —
1540
+ // daemon implement run, Claude Code, in-app builder — sees the approved
1541
+ // approach, not just a reference. Best-effort: never fail prompt generation
1542
+ // on a plan fetch error.
1543
+ if (cardData.plan_id) {
1544
+ try {
1545
+ const { plan } = await this.getPlan(cardData.plan_id);
1546
+ const planContent = (plan as { content?: string | null } | null)
1547
+ ?.content;
1548
+ if (planContent?.trim()) {
1549
+ result.prompt = `${result.prompt}\n\n## Approved Plan\nThis card has an approved implementation plan. Follow it unless you find a concrete reason it is wrong — if you must diverge, say so in a comment and explain why.\n\n${planContent.trim()}`;
1550
+ }
1551
+ } catch (err) {
1552
+ const msg = err instanceof Error ? err.message : String(err);
1553
+ console.debug(`[generateCardPrompt] plan fetch failed: ${msg}`);
1554
+ }
1555
+ }
1556
+
1557
+ // Persist a snapshot of the generated prompt. Best-effort — never fail
1558
+ // prompt generation just because logging didn't land. (The AGP-era
1559
+ // session_id / content_hash / template_version / confidence columns were
1560
+ // dropped in migration 20260508000000 — card #329.)
1551
1561
  try {
1552
1562
  await this.recordPromptHistory({
1553
1563
  cardId: cardData.id,
@@ -1558,10 +1568,6 @@ export class HarmonyApiClient {
1558
1568
  tokenEstimate: result.tokenEstimate,
1559
1569
  contextSummary: result.contextSummary,
1560
1570
  },
1561
- sessionId: options.sessionId ?? null,
1562
- contentHash: result.contentHash,
1563
- templateVersion: result.version,
1564
- confidence: 0.5,
1565
1571
  });
1566
1572
  } catch (err) {
1567
1573
  const msg = err instanceof Error ? err.message : String(err);
@@ -1610,6 +1616,8 @@ interface CardPromptData {
1610
1616
  }>;
1611
1617
  column_id?: string;
1612
1618
  project_id?: string;
1619
+ /** Linked plan (e.g. an agent-approved plan). Surfaced in the prompt. */
1620
+ plan_id?: string | null;
1613
1621
  }
1614
1622
 
1615
1623
  interface MemoryItem {
@@ -773,70 +773,3 @@ export function getAvailableCategories(): LabelCategory[] {
773
773
  export function getAvailableVariants(): PromptVariant[] {
774
774
  return ["analysis", "draft", "execute"];
775
775
  }
776
-
777
- // ─── Variant proposal (logged-only — no auto-commit) ──────────────────
778
-
779
- /** Cohort row shape consumed by {@link proposePromptVariant}. */
780
- export interface PromptCohortRow {
781
- /** Final agent session status — only "completed" is treated as success. */
782
- status: string | null;
783
- /** Final progress percent recorded on the linked session, when present. */
784
- progressPercent: number | null;
785
- /** Whether the linked session ended with non-empty blockers. */
786
- hadBlockers: boolean;
787
- }
788
-
789
- export interface PromptVariantSuggestion {
790
- contentHash: string;
791
- cohortSize: number;
792
- completionRate: number;
793
- framingHint: string;
794
- }
795
-
796
- const VARIANT_MIN_COHORT = 10;
797
- const VARIANT_COMPLETION_THRESHOLD = 0.4;
798
-
799
- /**
800
- * Propose an alternative framing for prompts with a given content hash, based
801
- * on observed session outcomes. Returns null when the cohort is too small or
802
- * the completion rate is acceptable.
803
- *
804
- * Per the AGP-P2 locked decision, this is logged-only — callers may surface
805
- * the suggestion to humans, but no auto-commit of new templates is allowed.
806
- *
807
- * @param fetchCohort — async loader that returns one row per session that
808
- * consumed a prompt with this hash. Keeps this module decoupled from the
809
- * API client so it stays pure-testable.
810
- */
811
- export async function proposePromptVariant(
812
- contentHash: string,
813
- fetchCohort: (hash: string) => Promise<PromptCohortRow[]>,
814
- ): Promise<PromptVariantSuggestion | null> {
815
- if (!contentHash) return null;
816
- const cohort = await fetchCohort(contentHash);
817
- if (!cohort || cohort.length < VARIANT_MIN_COHORT) return null;
818
-
819
- const completed = cohort.filter(
820
- (r) =>
821
- r.status === "completed" &&
822
- (r.progressPercent ?? 0) >= 100 &&
823
- !r.hadBlockers,
824
- ).length;
825
- const completionRate = completed / cohort.length;
826
-
827
- if (completionRate >= VARIANT_COMPLETION_THRESHOLD) return null;
828
-
829
- const blockerRate =
830
- cohort.filter((r) => r.hadBlockers).length / cohort.length;
831
- const framingHint =
832
- blockerRate >= 0.4
833
- ? "Cohort hits frequent blockers — try a more diagnostic framing (require root-cause + repro before any fix)."
834
- : "Cohort frequently stalls without finishing — try a more action-forcing framing (smaller subtasks, explicit DoD checklist).";
835
-
836
- return {
837
- contentHash,
838
- cohortSize: cohort.length,
839
- completionRate,
840
- framingHint,
841
- };
842
- }
package/src/server.ts CHANGED
@@ -295,7 +295,8 @@ export const TOOLS = {
295
295
  title: { type: "string", description: "Card title" },
296
296
  columnId: {
297
297
  type: "string",
298
- description: "Target column ID (optional, defaults to first column)",
298
+ description:
299
+ "Target column ID (optional, defaults to the project's default column, or the first column if none is set)",
299
300
  },
300
301
  projectId: {
301
302
  type: "string",
@@ -3644,12 +3645,22 @@ async function handleToolCall(
3644
3645
  | undefined,
3645
3646
  });
3646
3647
 
3647
- // Build URL for viewing the plan
3648
- const planUrl = `https://app.gethmy.com/plans/${(result.plan as { id: string }).id}`;
3648
+ // Build URL for viewing the plan. The real route is project-scoped
3649
+ // (/{workspaceSlug}/{projectSlug}/plans/{planId}); fall back to the flat
3650
+ // path only if the API didn't return slugs (older deployments).
3651
+ const planId = (result.plan as { id: string }).id;
3652
+ const { workspaceSlug, projectSlug } = result as {
3653
+ workspaceSlug?: string | null;
3654
+ projectSlug?: string | null;
3655
+ };
3656
+ const planUrl =
3657
+ workspaceSlug && projectSlug
3658
+ ? `https://app.gethmy.com/${workspaceSlug}/${projectSlug}/plans/${planId}`
3659
+ : `https://app.gethmy.com/plans/${planId}`;
3649
3660
 
3650
3661
  return {
3651
3662
  success: true,
3652
- planId: (result.plan as { id: string }).id,
3663
+ planId,
3653
3664
  planUrl,
3654
3665
  plan: result.plan,
3655
3666
  tasks: result.tasks,