metheus-governance-mcp-cli 0.2.151 → 0.2.152

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/cli.mjs CHANGED
@@ -130,10 +130,12 @@ import {
130
130
  printRunnerResult,
131
131
  } from "./lib/runner-helpers.mjs";
132
132
  import {
133
+ createProjectEvidence as createProjectEvidenceImpl,
133
134
  createProjectWorkItem as createProjectWorkItemImpl,
134
135
  createThreadComment as createThreadCommentImpl,
135
136
  createWorkItemThread as createWorkItemThreadImpl,
136
137
  discoverArchiveThreadForDestination as discoverArchiveThreadForDestinationImpl,
138
+ linkWorkItemEvidence as linkWorkItemEvidenceImpl,
137
139
  listProjectChatDestinations as listProjectChatDestinationsImpl,
138
140
  listThreadComments as listThreadCommentsImpl,
139
141
  listUserBotsForRunner as listUserBotsForRunnerImpl,
@@ -1362,7 +1364,6 @@ function loadBotRunnerWorkspaceRegistry(options = {}) {
1362
1364
  }
1363
1365
 
1364
1366
  function saveBotRunnerConfig(nextConfig, filePath = botRunnerConfigFilePath()) {
1365
- saveBotRunnerWorkspaceRegistry(safeObject(nextConfig).projectMappings);
1366
1367
  const payload = serializeBotRunnerConfig(nextConfig);
1367
1368
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
1368
1369
  fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
@@ -1523,6 +1524,9 @@ function loadBotRunnerConfig(options = {}) {
1523
1524
  normalized.projectMappings = mergedProjectMappings;
1524
1525
  normalized.workspaceRegistryFilePath = workspaceRegistry.filePath;
1525
1526
  if (persistIfNeeded && (normalized.migrated || !fs.existsSync(filePath) || registryChanged || legacyProjectMappingsPresent)) {
1527
+ if (registryChanged || legacyProjectMappingsPresent) {
1528
+ saveBotRunnerWorkspaceRegistry(mergedProjectMappings, workspaceRegistry.filePath);
1529
+ }
1526
1530
  saveBotRunnerConfig(normalized, filePath);
1527
1531
  normalized.migrated = false;
1528
1532
  }
@@ -1626,24 +1630,22 @@ function rememberProjectWorkspaceMapping({ projectID, workspaceDir, source }) {
1626
1630
  reason: "existing workspace mapping preserved over unrelated automatic signal",
1627
1631
  };
1628
1632
  }
1629
- const nextConfig = {
1630
- ...config,
1631
- projectMappings: {
1632
- ...safeObject(config.projectMappings),
1633
- [projectID]: {
1634
- projectID,
1635
- workspaceDir: normalizedWorkspaceDir,
1636
- source: normalizedSource,
1637
- updatedAt: new Date().toISOString(),
1638
- },
1633
+ const nextProjectMappings = {
1634
+ ...safeObject(config.projectMappings),
1635
+ [projectID]: {
1636
+ projectID,
1637
+ workspaceDir: normalizedWorkspaceDir,
1638
+ source: normalizedSource,
1639
+ updatedAt: new Date().toISOString(),
1639
1640
  },
1640
1641
  };
1641
- const filePath = saveBotRunnerConfig(nextConfig, config.filePath);
1642
+ const workspaceRegistryFilePath = config.workspaceRegistryFilePath || botRunnerWorkspaceRegistryFilePath();
1643
+ saveBotRunnerWorkspaceRegistry(nextProjectMappings, workspaceRegistryFilePath);
1642
1644
  return {
1643
1645
  ok: true,
1644
1646
  updated: true,
1645
- filePath,
1646
- workspaceRegistryFilePath: nextConfig.workspaceRegistryFilePath || botRunnerWorkspaceRegistryFilePath(),
1647
+ filePath: config.filePath,
1648
+ workspaceRegistryFilePath,
1647
1649
  projectID,
1648
1650
  workspaceDir: normalizedWorkspaceDir,
1649
1651
  };
@@ -3286,10 +3288,18 @@ async function createProjectWorkItem(params) {
3286
3288
  return createProjectWorkItemImpl(params, buildRunnerDataDeps());
3287
3289
  }
3288
3290
 
3291
+ async function createProjectEvidence(params) {
3292
+ return createProjectEvidenceImpl(params, buildRunnerDataDeps());
3293
+ }
3294
+
3289
3295
  async function createWorkItemThread(params) {
3290
3296
  return createWorkItemThreadImpl(params, buildRunnerDataDeps());
3291
3297
  }
3292
3298
 
3299
+ async function linkWorkItemEvidence(params) {
3300
+ return linkWorkItemEvidenceImpl(params, buildRunnerDataDeps());
3301
+ }
3302
+
3293
3303
  function buildRunnerDeliveryDeps() {
3294
3304
  return {
3295
3305
  listProjectChatDestinations,
@@ -3324,8 +3334,10 @@ function buildRunnerExecutionDeps() {
3324
3334
  resolveRunnerExecutionPlanForRole,
3325
3335
  validateWorkspaceArtifacts,
3326
3336
  tryJsonParse,
3337
+ createProjectEvidence,
3327
3338
  createProjectWorkItem,
3328
3339
  createWorkItemThread,
3340
+ linkWorkItemEvidence,
3329
3341
  replaceProjectCtxpackFiles,
3330
3342
  };
3331
3343
  }
@@ -7380,13 +7392,6 @@ function syncCtxpackToLocalCache({
7380
7392
  } catch {
7381
7393
  // Best-effort metadata refresh for workspace-root auto-detection.
7382
7394
  }
7383
- if (!isHomeFallback) {
7384
- rememberProjectWorkspaceMapping({
7385
- projectID,
7386
- workspaceDir: resolvedWorkspaceDir,
7387
- source: "ctxpack_sync_current",
7388
- });
7389
- }
7390
7395
  return {
7391
7396
  sync_status: isHomeFallback ? "home_fallback" : "current",
7392
7397
  sync_message: isHomeFallback
@@ -7410,14 +7415,6 @@ function syncCtxpackToLocalCache({
7410
7415
 
7411
7416
  fs.writeFileSync(metaPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
7412
7417
  fs.writeFileSync(workspaceMetaPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
7413
- if (!isHomeFallback) {
7414
- rememberProjectWorkspaceMapping({
7415
- projectID,
7416
- workspaceDir: resolvedWorkspaceDir,
7417
- source: "ctxpack_sync_write",
7418
- });
7419
- }
7420
-
7421
7418
  return {
7422
7419
  sync_status: isHomeFallback ? "home_fallback" : (previousMeta ? "updated" : "downloaded"),
7423
7420
  sync_message: isHomeFallback
@@ -8505,14 +8502,14 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
8505
8502
  safeObject(runnerConfigAfterProjectTool.projectMappings)?.[selftestProjectID]?.workspaceDir || "";
8506
8503
  const projectToolWorkspaceSignalOk =
8507
8504
  String(safeObject(response).error || "").trim() === ""
8508
- && normalizedPathForCompare(storedProjectToolWorkspace) === normalizedPathForCompare(projectToolWorkspaceDir);
8505
+ && !String(storedProjectToolWorkspace || "").trim();
8509
8506
  push(
8510
- "project_summary_workspace_signal_updates_runner_mapping",
8507
+ "project_summary_workspace_signal_does_not_update_runner_mapping",
8511
8508
  projectToolWorkspaceSignalOk,
8512
8509
  `workspace=${storedProjectToolWorkspace || "(none)"} source=${String(safeObject(runnerConfigAfterProjectTool.projectMappings)?.[selftestProjectID]?.source || "(none)")}`,
8513
8510
  );
8514
8511
  } catch (err) {
8515
- push("project_summary_workspace_signal_updates_runner_mapping", false, String(err?.message || err));
8512
+ push("project_summary_workspace_signal_does_not_update_runner_mapping", false, String(err?.message || err));
8516
8513
  } finally {
8517
8514
  if (previousProjectToolUserProfile === undefined) delete process.env.USERPROFILE;
8518
8515
  else process.env.USERPROFILE = previousProjectToolUserProfile;
@@ -8582,12 +8579,12 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
8582
8579
  const storedWorkspaceAfterDrift =
8583
8580
  safeObject(runnerConfigAfterProjectToolDrift.projectMappings)?.[selftestProjectID]?.workspaceDir || "";
8584
8581
  push(
8585
- "project_summary_workspace_signal_does_not_override_unrelated_existing_mapping",
8582
+ "project_summary_workspace_signal_preserves_existing_manual_mapping",
8586
8583
  normalizedPathForCompare(storedWorkspaceAfterDrift) === normalizedPathForCompare(stableWorkspaceDir),
8587
8584
  `stored=${storedWorkspaceAfterDrift || "(none)"} stable=${stableWorkspaceDir}`,
8588
8585
  );
8589
8586
  } catch (err) {
8590
- push("project_summary_workspace_signal_does_not_override_unrelated_existing_mapping", false, String(err?.message || err));
8587
+ push("project_summary_workspace_signal_preserves_existing_manual_mapping", false, String(err?.message || err));
8591
8588
  } finally {
8592
8589
  if (previousProjectToolDriftUserProfile === undefined) delete process.env.USERPROFILE;
8593
8590
  else process.env.USERPROFILE = previousProjectToolDriftUserProfile;
@@ -49,7 +49,6 @@ export async function handleLocalProjectToolDispatch(
49
49
  const normalizeMergeAction = requireDependency(deps, "normalizeMergeAction");
50
50
  const executeCtxpackMergeActionForTool = requireDependency(deps, "executeCtxpackMergeActionForTool");
51
51
  const buildCtxpackMergeExecuteText = requireDependency(deps, "buildCtxpackMergeExecuteText");
52
- const rememberProjectWorkspaceMapping = requireDependency(deps, "rememberProjectWorkspaceMapping");
53
52
  const jsonRpcError = requireDependency(deps, "jsonRpcError");
54
53
  const jsonRpcResult = requireDependency(deps, "jsonRpcResult");
55
54
 
@@ -91,17 +90,6 @@ export async function handleLocalProjectToolDispatch(
91
90
  workspaceDir,
92
91
  workspaceSignalTrusted,
93
92
  });
94
- if (workspaceSignalTrusted && String(workspaceDir || "").trim()) {
95
- try {
96
- rememberProjectWorkspaceMapping({
97
- projectID,
98
- workspaceDir,
99
- source: "project_tool_workspace_signal",
100
- });
101
- } catch {
102
- // Best-effort persistence only. The project summary response itself should still succeed.
103
- }
104
- }
105
93
  const text = buildProjectSummaryText(summary);
106
94
  return jsonRpcResult(requestObj, {
107
95
  content: [{ type: "text", text }],
@@ -281,6 +281,94 @@ export async function createWorkItemThread(
281
281
  return safeObject(parseJSONText(responseText));
282
282
  }
283
283
 
284
+ export async function createProjectEvidence(
285
+ {
286
+ siteBaseURL,
287
+ token,
288
+ timeoutSeconds,
289
+ actorUserID,
290
+ projectID,
291
+ type,
292
+ uri = "",
293
+ source = "",
294
+ credibility = "",
295
+ sensitivity = "",
296
+ reproducibility = "",
297
+ version = "",
298
+ hash = "",
299
+ storage = "",
300
+ repoURL = "",
301
+ commitSHA = "",
302
+ repoPath = "",
303
+ blobSHA = "",
304
+ sha256 = "",
305
+ sizeBytes,
306
+ mimeType = "",
307
+ },
308
+ deps,
309
+ ) {
310
+ const postJSONWithAuthHeaders = requireDependency(deps, "postJSONWithAuthHeaders");
311
+ const parseJSONText = requireDependency(deps, "parseJSONText");
312
+ const payload = {
313
+ project_id: projectID,
314
+ type,
315
+ ...(uri ? { uri } : {}),
316
+ ...(source ? { source } : {}),
317
+ ...(credibility ? { credibility } : {}),
318
+ ...(sensitivity ? { sensitivity } : {}),
319
+ ...(reproducibility ? { reproducibility } : {}),
320
+ ...(version ? { version } : {}),
321
+ ...(hash ? { hash } : {}),
322
+ ...(storage ? { storage } : {}),
323
+ ...(repoURL ? { repo_url: repoURL } : {}),
324
+ ...(commitSHA ? { commit_sha: commitSHA } : {}),
325
+ ...(repoPath ? { repo_path: repoPath } : {}),
326
+ ...(blobSHA ? { blob_sha: blobSHA } : {}),
327
+ ...(sha256 ? { sha256 } : {}),
328
+ ...(Number.isFinite(Number(sizeBytes)) ? { size_bytes: Number(sizeBytes) } : {}),
329
+ ...(mimeType ? { mime_type: mimeType } : {}),
330
+ };
331
+ const responseText = await postJSONWithAuthHeaders(
332
+ `${siteBaseURL}/api/v1/evidence`,
333
+ timeoutSeconds,
334
+ token,
335
+ payload,
336
+ {
337
+ "X-Actor-User-Id": actorUserID,
338
+ },
339
+ );
340
+ return safeObject(parseJSONText(responseText));
341
+ }
342
+
343
+ export async function linkWorkItemEvidence(
344
+ {
345
+ siteBaseURL,
346
+ token,
347
+ timeoutSeconds,
348
+ actorUserID,
349
+ workItemID,
350
+ evidenceID,
351
+ relationNote = "",
352
+ },
353
+ deps,
354
+ ) {
355
+ const postJSONWithAuthHeaders = requireDependency(deps, "postJSONWithAuthHeaders");
356
+ const parseJSONText = requireDependency(deps, "parseJSONText");
357
+ const responseText = await postJSONWithAuthHeaders(
358
+ `${siteBaseURL}/api/v1/workitems/${encodeURIComponent(workItemID)}/evidence`,
359
+ timeoutSeconds,
360
+ token,
361
+ {
362
+ evidence_id: evidenceID,
363
+ relation_note: relationNote,
364
+ },
365
+ {
366
+ "X-Actor-User-Id": actorUserID,
367
+ },
368
+ );
369
+ return safeObject(parseJSONText(responseText));
370
+ }
371
+
284
372
  export async function discoverArchiveThreadForDestination(
285
373
  {
286
374
  siteBaseURL,
@@ -586,7 +586,14 @@ async function materializeExecutionWorkItems({
586
586
  const createWorkItemThread = typeof executionDeps.createWorkItemThread === "function"
587
587
  ? executionDeps.createWorkItemThread
588
588
  : null;
589
+ const createProjectEvidence = typeof executionDeps.createProjectEvidence === "function"
590
+ ? executionDeps.createProjectEvidence
591
+ : null;
592
+ const linkWorkItemEvidence = typeof executionDeps.linkWorkItemEvidence === "function"
593
+ ? executionDeps.linkWorkItemEvidence
594
+ : null;
589
595
  const createdWorkItems = [];
596
+ const createdEvidenceItems = [];
590
597
  const errors = [];
591
598
  for (const item of requestedWorkItems) {
592
599
  try {
@@ -634,8 +641,66 @@ async function materializeExecutionWorkItems({
634
641
  errors.push(`failed to create work item "${item.title}": ${String(err?.message || err)}`);
635
642
  }
636
643
  }
644
+ if (requiresEvidenceLinkForExecutionStep(step) && createdWorkItems.length > 0) {
645
+ if (!createProjectEvidence || !linkWorkItemEvidence) {
646
+ errors.push("runner execution deps missing evidence creation/link helpers");
647
+ } else if (ensureArray(validatedArtifacts).length === 0) {
648
+ errors.push(`could not link evidence for execution step "${String(safeObject(step).goal || "").trim() || "unnamed"}" because no validated project artifacts were produced`);
649
+ } else {
650
+ for (const artifactRaw of ensureArray(validatedArtifacts)) {
651
+ const artifact = safeObject(artifactRaw);
652
+ const artifactPath = String(artifact.relativePath || artifact.path || "").trim();
653
+ if (!artifactPath) {
654
+ continue;
655
+ }
656
+ try {
657
+ const createdEvidence = safeObject(await createProjectEvidence({
658
+ siteBaseURL: runtime.baseURL,
659
+ token: runtime.token,
660
+ timeoutSeconds: runtime.timeoutSeconds,
661
+ actorUserID: safeObject(runtime.actor).user_id,
662
+ projectID: String(normalizedRoute.projectID || normalizedRoute.project_id || "").trim(),
663
+ type: "file",
664
+ uri: artifactPath,
665
+ source: "runner_execution",
666
+ }));
667
+ const evidenceID = String(createdEvidence.id || createdEvidence.evidence_id || createdEvidence.evidenceID || "").trim();
668
+ if (!evidenceID) {
669
+ errors.push(`failed to create evidence for artifact "${artifactPath}"`);
670
+ continue;
671
+ }
672
+ createdEvidenceItems.push({
673
+ id: evidenceID,
674
+ path: artifactPath,
675
+ });
676
+ for (const workItem of createdWorkItems) {
677
+ const workItemID = String(safeObject(workItem).id || "").trim();
678
+ if (!workItemID) {
679
+ continue;
680
+ }
681
+ try {
682
+ await linkWorkItemEvidence({
683
+ siteBaseURL: runtime.baseURL,
684
+ token: runtime.token,
685
+ timeoutSeconds: runtime.timeoutSeconds,
686
+ actorUserID: safeObject(runtime.actor).user_id,
687
+ workItemID,
688
+ evidenceID,
689
+ relationNote: `Execution artifact: ${artifactPath}`,
690
+ });
691
+ } catch (err) {
692
+ errors.push(`failed to link evidence "${artifactPath}" to work item "${String(safeObject(workItem).title || workItemID).trim() || workItemID}": ${String(err?.message || err)}`);
693
+ }
694
+ }
695
+ } catch (err) {
696
+ errors.push(`failed to create evidence for artifact "${artifactPath}": ${String(err?.message || err)}`);
697
+ }
698
+ }
699
+ }
700
+ }
637
701
  return {
638
702
  workItems: createdWorkItems,
703
+ evidenceItems: createdEvidenceItems,
639
704
  errors,
640
705
  };
641
706
  }
@@ -1148,6 +1213,7 @@ async function maybeExecuteDynamicRolePlan({
1148
1213
  const mergedArtifactPaths = new Set();
1149
1214
  const mergedBoundaryViolations = [];
1150
1215
  const mergedWorkItems = [];
1216
+ const mergedEvidenceItems = [];
1151
1217
  const mergedCtxpackChangedPaths = new Set();
1152
1218
  let finalCtxpackUpdate = null;
1153
1219
  const totalSteps = plannedSteps.length;
@@ -1318,6 +1384,7 @@ async function maybeExecuteDynamicRolePlan({
1318
1384
  selectedRecord,
1319
1385
  });
1320
1386
  createdWorkItems = ensureArray(workItemMaterialization.workItems);
1387
+ const createdEvidenceItems = ensureArray(workItemMaterialization.evidenceItems);
1321
1388
  stepErrors.push(
1322
1389
  ...ensureArray(workItemMaterialization.errors)
1323
1390
  .map((item) => String(item || "").trim())
@@ -1326,6 +1393,17 @@ async function maybeExecuteDynamicRolePlan({
1326
1393
  if (requiresWorkItemsForExecutionStep(step) && createdWorkItems.length === 0) {
1327
1394
  stepErrors.push(`${String(step.role || "").trim()} step completed without creating any governance work items`);
1328
1395
  }
1396
+ createdEvidenceItems.forEach((item) => {
1397
+ const evidence = safeObject(item);
1398
+ const evidenceID = String(evidence.id || "").trim();
1399
+ if (!evidenceID) {
1400
+ return;
1401
+ }
1402
+ if (mergedEvidenceItems.some((existing) => String(safeObject(existing).id || "").trim() === evidenceID)) {
1403
+ return;
1404
+ }
1405
+ mergedEvidenceItems.push(evidence);
1406
+ });
1329
1407
  }
1330
1408
  if (stepErrors.length > 0) {
1331
1409
  const reason = stepErrors.join("; ");
@@ -1387,6 +1465,7 @@ async function maybeExecuteDynamicRolePlan({
1387
1465
  artifactValidation: String(stepValidation.status || "").trim() || "none",
1388
1466
  ctxpack: ctxpackUpdate,
1389
1467
  workItems: createdWorkItems,
1468
+ evidenceItems: mergedEvidenceItems,
1390
1469
  });
1391
1470
  }
1392
1471
  const finalStepResult = stepResults[stepResults.length - 1] || {};
@@ -1431,6 +1510,10 @@ async function maybeExecuteDynamicRolePlan({
1431
1510
  description: String(safeObject(item).description || "").trim(),
1432
1511
  thread_id: String(safeObject(item).thread_id || "").trim(),
1433
1512
  })),
1513
+ evidenceItems: mergedEvidenceItems.map((item) => ({
1514
+ id: String(safeObject(item).id || "").trim(),
1515
+ path: String(safeObject(item).path || "").trim(),
1516
+ })),
1434
1517
  ctxpackUpdate: finalCtxpackUpdate && Object.keys(finalCtxpackUpdate).length > 0
1435
1518
  ? {
1436
1519
  ctxpack_id: String(finalCtxpackUpdate.ctxpackID || "").trim(),
@@ -3050,6 +3133,8 @@ export async function processRunnerSelectedRecord({
3050
3133
  last_boundary_violations: [],
3051
3134
  last_work_item_ids: ensureArray(aiResult?.workItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
3052
3135
  last_work_item_titles: ensureArray(aiResult?.workItems).map((item) => String(safeObject(item).title || "").trim()).filter(Boolean),
3136
+ last_evidence_ids: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
3137
+ last_evidence_paths: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).path || "").trim()).filter(Boolean),
3053
3138
  last_ctxpack_version_id: String(safeObject(aiResult?.ctxpackUpdate).version_id || "").trim(),
3054
3139
  last_ctxpack_changed_paths: ensureArray(safeObject(aiResult?.ctxpackUpdate).changed_paths).map((item) => String(item || "").trim()).filter(Boolean),
3055
3140
  last_ctxpack_pushed_file_count: intFromRawAllowZero(safeObject(aiResult?.ctxpackUpdate).pushed_file_count, 0),
@@ -3099,6 +3184,8 @@ export async function processRunnerSelectedRecord({
3099
3184
  ctxpack_pushed_file_count: intFromRawAllowZero(safeObject(aiResult?.ctxpackUpdate).pushed_file_count, 0),
3100
3185
  work_item_ids: ensureArray(aiResult?.workItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
3101
3186
  work_item_titles: ensureArray(aiResult?.workItems).map((item) => String(safeObject(item).title || "").trim()).filter(Boolean),
3187
+ evidence_ids: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
3188
+ evidence_paths: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).path || "").trim()).filter(Boolean),
3102
3189
  reply_chars: String(sanitizedReplyText || "").length,
3103
3190
  execution_mode: executionPlan.mode,
3104
3191
  role_profile: executionPlan.roleProfileName,
@@ -3887,6 +3887,8 @@ export async function runSelftestRunnerScenarios(push, deps) {
3887
3887
  let deliveredText = "";
3888
3888
  let createWorkItemCalls = 0;
3889
3889
  let createThreadCalls = 0;
3890
+ let createEvidenceCalls = 0;
3891
+ let linkEvidenceCalls = 0;
3890
3892
  let ctxpackPushCalls = 0;
3891
3893
  const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-workitems-"));
3892
3894
  const processed = await processRunnerSelectedRecord({
@@ -4063,6 +4065,20 @@ export async function runSelftestRunnerScenarios(push, deps) {
4063
4065
  thread_id: `${workItemID}-thread`,
4064
4066
  };
4065
4067
  },
4068
+ createProjectEvidence: async ({ uri }) => {
4069
+ createEvidenceCalls += 1;
4070
+ return {
4071
+ id: `evidence-${createEvidenceCalls}`,
4072
+ evidence_id: `evidence-${createEvidenceCalls}`,
4073
+ uri,
4074
+ };
4075
+ },
4076
+ linkWorkItemEvidence: async () => {
4077
+ linkEvidenceCalls += 1;
4078
+ return {
4079
+ linked: true,
4080
+ };
4081
+ },
4066
4082
  replaceProjectCtxpackFiles: async ({ artifacts }) => {
4067
4083
  ctxpackPushCalls += 1;
4068
4084
  return {
@@ -4106,12 +4122,15 @@ export async function runSelftestRunnerScenarios(push, deps) {
4106
4122
  && deliveryCalls === 1
4107
4123
  && createWorkItemCalls === 2
4108
4124
  && createThreadCalls === 2
4125
+ && createEvidenceCalls === 1
4126
+ && linkEvidenceCalls === 2
4109
4127
  && ctxpackPushCalls === 1
4110
4128
  && ensureArray(processed.result?.work_item_ids).length === 2
4129
+ && ensureArray(processed.result?.evidence_ids).length === 1
4111
4130
  && ensureArray(processed.result?.executed_role_plan?.created_work_items).length === 2
4112
4131
  && String(processed.result?.ctxpack_version_id || "") === "version-1"
4113
4132
  && /implementation plan/i.test(deliveredText),
4114
- `kind=${String(processed.kind || "(none)")} delivery_calls=${deliveryCalls} delivered=${deliveredText} created_work_items=${ensureArray(processed.result?.work_item_ids).join(",")} threads=${createThreadCalls} ctxpack_pushes=${ctxpackPushCalls}`,
4133
+ `kind=${String(processed.kind || "(none)")} delivery_calls=${deliveryCalls} delivered=${deliveredText} created_work_items=${ensureArray(processed.result?.work_item_ids).join(",")} evidence_ids=${ensureArray(processed.result?.evidence_ids).join(",")} threads=${createThreadCalls} evidence_links=${linkEvidenceCalls} ctxpack_pushes=${ctxpackPushCalls}`,
4115
4134
  );
4116
4135
  } catch (err) {
4117
4136
  push("single_bot_execution_request_materializes_governance_work_items", false, String(err?.message || err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.151",
3
+ "version": "0.2.152",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [