metheus-governance-mcp-cli 0.2.228 → 0.2.230

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.
@@ -1,6 +1,48 @@
1
1
  import http from "node:http";
2
2
  import https from "node:https";
3
3
 
4
+ function sleep(ms) {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+
8
+ function isTransientGatewayTransportError(err) {
9
+ const code = String(err?.code || "").trim().toUpperCase();
10
+ const text = String(err?.message || err || "").toLowerCase();
11
+ if ([
12
+ "ECONNRESET",
13
+ "EPIPE",
14
+ "ETIMEDOUT",
15
+ "ECONNABORTED",
16
+ "EAI_AGAIN",
17
+ ].includes(code)) {
18
+ return true;
19
+ }
20
+ return [
21
+ "timeout",
22
+ "socket hang up",
23
+ "bad record mac",
24
+ "decryption failed",
25
+ "tls_get_more_records",
26
+ ].some((item) => text.includes(item));
27
+ }
28
+
29
+ async function withTransientGatewayRetry(operation) {
30
+ const maxAttempts = 3;
31
+ let lastError = null;
32
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
33
+ try {
34
+ return await Promise.resolve(operation(attempt));
35
+ } catch (err) {
36
+ lastError = err;
37
+ if (attempt >= maxAttempts || !isTransientGatewayTransportError(err)) {
38
+ throw err;
39
+ }
40
+ await sleep(250 * attempt);
41
+ }
42
+ }
43
+ throw lastError || new Error("gateway transport failed");
44
+ }
45
+
4
46
  function safeObject(value) {
5
47
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
6
48
  return value;
@@ -32,7 +74,7 @@ export function parseToolEnvelopeFromRPCResult(resultObj, deps) {
32
74
  }
33
75
 
34
76
  export async function postJSON(urlText, timeoutSeconds, token, payload) {
35
- return new Promise((resolve, reject) => {
77
+ return withTransientGatewayRetry(() => new Promise((resolve, reject) => {
36
78
  const url = new URL(urlText);
37
79
  const body = Buffer.from(JSON.stringify(payload));
38
80
  const transport = url.protocol === "http:" ? http : https;
@@ -74,11 +116,11 @@ export async function postJSON(urlText, timeoutSeconds, token, payload) {
74
116
  req.on("error", reject);
75
117
  req.write(body);
76
118
  req.end();
77
- });
119
+ }));
78
120
  }
79
121
 
80
122
  export function getJSONWithAuthHeaders(urlText, timeoutSeconds, token, extraHeaders = {}) {
81
- return new Promise((resolve, reject) => {
123
+ return withTransientGatewayRetry(() => new Promise((resolve, reject) => {
82
124
  const url = new URL(urlText);
83
125
  const transport = url.protocol === "http:" ? http : https;
84
126
  const req = transport.request(
@@ -121,7 +163,7 @@ export function getJSONWithAuthHeaders(urlText, timeoutSeconds, token, extraHead
121
163
  });
122
164
  req.on("error", reject);
123
165
  req.end();
124
- });
166
+ }));
125
167
  }
126
168
 
127
169
  export function getJSONWithAuth(urlText, timeoutSeconds, token) {
@@ -2158,8 +2158,11 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
2158
2158
  lines.push(
2159
2159
  "As an expected follow-up responder, contribute the assigned perspective or delegated result now.",
2160
2160
  "A valid follow-up may be a concise opinion, analysis, review, comparison, synthesis, or direct delegated result.",
2161
+ "Do not ask the human to restate the project goal, current status, or blockers before contributing your assigned perspective.",
2162
+ "Use the current room context and execution contract first. If context is limited, state your assumptions explicitly and still provide the best concise contribution you can now.",
2161
2163
  "Mentioning the lead bot for acknowledgment or handoff does not require a new delegation contract.",
2162
2164
  "Do not wake any third bot. Only the lead bot may delegate new public work in this conversation.",
2165
+ "Do not create a new delegation contract unless the current contract explicitly authorizes this bot to hand work onward.",
2163
2166
  "If any other expected responder besides this bot still remains in the current execution contract, do not emit contract.type=\"summary_request\" yet.",
2164
2167
  "Only the final remaining contributor may hand control back to the designated summary bot with contract.type=\"summary_request\".",
2165
2168
  );
@@ -5411,36 +5411,9 @@ export async function processRunnerSelectedRecord({
5411
5411
  },
5412
5412
  };
5413
5413
  }
5414
- if (
5415
- conversationContext?.mode === "public_multi_bot"
5416
- && String(conversationContext.intentMode || "").trim() === "delegated_single_lead"
5417
- && currentBotSelector
5418
- && currentBotSelector !== normalizeMentionSelector(conversationContext.leadBotUsername)
5419
- && executionContract?.type === "delegation"
5420
- ) {
5421
- const reason = "only the delegated lead bot may issue new public assignments";
5422
- saveRunnerRouteState(
5423
- routeKey,
5424
- buildRunnerRouteStateFromComment(selectedRecord, {
5425
- last_action: "conversation_skipped",
5426
- last_reason: reason,
5427
- last_conversation_id: String(conversationContext?.id || "").trim(),
5428
- last_conversation_stage: String(conversationContext?.stage || "").trim(),
5429
- ...intentStatePatch,
5430
- }),
5431
- );
5432
- return {
5433
- kind: "skipped",
5434
- skippedRecord: {
5435
- id: selectedRecord.id,
5436
- reason,
5437
- messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
5438
- },
5439
- };
5440
- }
5441
5414
  const replyToMessageID = aiResult.replyToMessageID > 0
5442
- ? aiResult.replyToMessageID
5443
- : intFromRawAllowZero(selectedRecord.parsedArchive?.messageID, 0);
5415
+ ? aiResult.replyToMessageID
5416
+ : intFromRawAllowZero(selectedRecord.parsedArchive?.messageID, 0);
5444
5417
  const sanitizedReplyText = sanitizeForcedDirectReplyText({
5445
5418
  replyText: aiResult.reply,
5446
5419
  bot,
@@ -5476,6 +5449,80 @@ export async function processRunnerSelectedRecord({
5476
5449
  };
5477
5450
  }
5478
5451
  }
5452
+ if (
5453
+ responseContractValidation.ok
5454
+ && effectiveConversationContext?.mode === "public_multi_bot"
5455
+ && String(effectiveConversationContext.intentMode || "").trim() === "delegated_single_lead"
5456
+ && currentBotSelector
5457
+ && currentBotSelector !== normalizeMentionSelector(effectiveConversationContext.leadBotUsername)
5458
+ && normalizedExecutionContractType === "delegation"
5459
+ ) {
5460
+ const unauthorizedDelegationTargets = uniqueOrdered([
5461
+ ...ensureArray(executionContract?.assignments)
5462
+ .map((item) => normalizeMentionSelector(item?.targetBot))
5463
+ .filter(Boolean),
5464
+ ...collectExecutionContractNextResponders(executionContract)
5465
+ .map((item) => normalizeMentionSelector(item))
5466
+ .filter(Boolean),
5467
+ ]).filter((item) => item && item !== currentBotSelector);
5468
+ if (unauthorizedDelegationTargets.length > 0) {
5469
+ responseContractValidation = {
5470
+ ok: false,
5471
+ status: "non_lead_public_assignment",
5472
+ reason: "only the delegated lead bot may issue new public assignments",
5473
+ targets: unauthorizedDelegationTargets,
5474
+ };
5475
+ }
5476
+ }
5477
+ if (!responseContractValidation.ok && String(responseContractValidation.status || "").trim() === "non_lead_public_assignment") {
5478
+ const reason = String(responseContractValidation.reason || "").trim()
5479
+ || "response contract validation failed";
5480
+ saveRunnerRouteState(
5481
+ routeKey,
5482
+ buildRunnerRouteStateFromComment(selectedRecord, {
5483
+ last_action: "conversation_skipped",
5484
+ last_reason: reason,
5485
+ last_conversation_id: String(effectiveConversationContext?.id || "").trim(),
5486
+ last_conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
5487
+ last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5488
+ last_contract_validation_reason: reason,
5489
+ last_contract_validation_targets: ensureArray(responseContractValidation.targets),
5490
+ ...intentStatePatch,
5491
+ }),
5492
+ );
5493
+ return {
5494
+ kind: "skipped",
5495
+ skippedRecord: {
5496
+ id: selectedRecord.id,
5497
+ reason,
5498
+ messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
5499
+ },
5500
+ result: {
5501
+ route_key: routeKey,
5502
+ route_name: normalizedRoute.name,
5503
+ outcome: "contract_validation_failed",
5504
+ detail: reason,
5505
+ thread_id: archiveThread.threadID,
5506
+ comment_id: selectedRecord.id,
5507
+ trigger_kind: String(effectiveTriggerDecision.trigger || "").trim(),
5508
+ conversation_id: String(effectiveConversationContext?.id || "").trim(),
5509
+ conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
5510
+ conversation_intent_mode: String(effectiveConversationContext?.intentMode || "").trim(),
5511
+ conversation_lead_bot: String(effectiveConversationContext?.leadBotUsername || "").trim(),
5512
+ conversation_summary_bot: String(effectiveConversationContext?.summaryBotUsername || "").trim(),
5513
+ execution_contract_type: String(executionContract?.type || "").trim(),
5514
+ execution_contract_actionable: executionContract?.actionable === true,
5515
+ execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
5516
+ next_expected_responders: collectExecutionContractNextResponders(executionContract),
5517
+ response_contract_validation_status: String(responseContractValidation.status || "").trim(),
5518
+ response_contract_validation_reason: reason,
5519
+ response_contract_validation_targets: ensureArray(responseContractValidation.targets),
5520
+ assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5521
+ assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5522
+ assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
5523
+ },
5524
+ };
5525
+ }
5479
5526
  if (effectiveConversationContext?.mode === "public_multi_bot") {
5480
5527
  const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
5481
5528
  const currentSession = safeObject(effectiveConversationContext.session);
@@ -9505,12 +9505,291 @@ export async function runSelftestRunnerScenarios(push, deps) {
9505
9505
  && !processed.skippedRecord,
9506
9506
  `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9507
9507
  );
9508
- } catch (err) {
9509
- push("delegated_single_lead_peer_to_peer_reply_reaches_ai_decision", false, String(err?.message || err));
9510
- }
9511
-
9512
- try {
9513
- const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-artifacts-ok-"));
9508
+ } catch (err) {
9509
+ push("delegated_single_lead_peer_to_peer_reply_reaches_ai_decision", false, String(err?.message || err));
9510
+ }
9511
+
9512
+ try {
9513
+ let aiCalls = 0;
9514
+ const processed = await processRunnerSelectedRecord({
9515
+ routeKey: "delegated-single-lead-non-lead-summary-request-allowed-key",
9516
+ normalizedRoute: normalizeRunnerRoute({
9517
+ name: "telegram-monitor-delegated-single-lead-non-lead-summary-request-allowed",
9518
+ project_id: selftestProjectID,
9519
+ provider: "telegram",
9520
+ role: "monitor",
9521
+ role_profile: "monitor",
9522
+ destination_id: "dest-1",
9523
+ destination_label: "Main Room",
9524
+ server_bot_name: "RyoAI3_bot",
9525
+ server_bot_id: "bot-peer-2",
9526
+ trigger_policy: {
9527
+ mentions_only: true,
9528
+ direct_messages: true,
9529
+ reply_to_bot_messages: true,
9530
+ },
9531
+ archive_policy: {
9532
+ mirror_replies: true,
9533
+ dedupe_inbound: true,
9534
+ dedupe_outbound: true,
9535
+ skip_bot_messages: true,
9536
+ },
9537
+ dry_run_delivery: true,
9538
+ }),
9539
+ selectedRecord: {
9540
+ id: "comment-delegated-single-lead-non-lead-summary-request-allowed",
9541
+ createdAt: "2026-03-16T00:02:21.000Z",
9542
+ parsedArchive: {
9543
+ kind: "bot_reply",
9544
+ conversationID: "comment-delegated-single-lead-open",
9545
+ conversationMode: "public_multi_bot",
9546
+ conversationStage: "bot_reply",
9547
+ conversationIntentMode: "delegated_single_lead",
9548
+ conversationAllowBotToBot: true,
9549
+ conversationLeadBotUsername: "ryoai_bot",
9550
+ conversationParticipants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9551
+ conversationInitialResponders: ["ryoai_bot"],
9552
+ conversationAllowedResponders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9553
+ conversationSummaryBotUsername: "ryoai_bot",
9554
+ botUsername: "RyoAI_bot",
9555
+ botName: "RyoAI_bot",
9556
+ sender: "RyoAI_bot",
9557
+ body: "@RyoAI3_bot Review the biggest implementation risk.",
9558
+ mentionUsernames: ["ryoai3_bot"],
9559
+ executionContract: {
9560
+ type: "delegation",
9561
+ actionable: true,
9562
+ assignments: [
9563
+ { target_bot: "ryoai3_bot", task: "Review the biggest implementation risk.", mode: "conversation_contribution" },
9564
+ ],
9565
+ summary_bot: "ryoai_bot",
9566
+ next_responders: ["ryoai3_bot"],
9567
+ },
9568
+ },
9569
+ },
9570
+ pendingOrdered: [],
9571
+ bot: {
9572
+ id: "bot-peer-2",
9573
+ name: "RyoAI3_bot",
9574
+ username: "RyoAI3_bot",
9575
+ role: "monitor",
9576
+ provider: "telegram",
9577
+ },
9578
+ destination: {
9579
+ id: "dest-1",
9580
+ label: "Main Room",
9581
+ provider: "telegram",
9582
+ chatID: "-100123",
9583
+ },
9584
+ archiveThread: {
9585
+ threadID: "thread-1",
9586
+ workItemID: "work-item-1",
9587
+ },
9588
+ executionPlan: {
9589
+ mode: "role_profile",
9590
+ roleProfileName: "monitor",
9591
+ roleProfile: {
9592
+ client: "sample",
9593
+ model: "",
9594
+ permissionMode: "read_only",
9595
+ reasoningEffort: "low",
9596
+ },
9597
+ workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-non-lead-summary-request-allowed"),
9598
+ workspaceSource: "selftest",
9599
+ usedCommandFallback: false,
9600
+ },
9601
+ runtime: {
9602
+ baseURL: "https://example.test",
9603
+ token: "selftest-token",
9604
+ timeoutSeconds: 30,
9605
+ actor: { user_id: "user-1" },
9606
+ },
9607
+ deps: {
9608
+ saveRunnerRouteState: () => {},
9609
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
9610
+ runRunnerAIExecution: async () => {
9611
+ aiCalls += 1;
9612
+ return {
9613
+ skip: false,
9614
+ reply: "현재 가장 큰 구현 리스크는 요구사항 경계가 아직 불명확하다는 점입니다. @RyoAI_bot 방향 정리를 부탁드립니다.",
9615
+ contract: {
9616
+ type: "summary_request",
9617
+ actionable: true,
9618
+ assignments: [],
9619
+ summary_bot: "ryoai_bot",
9620
+ next_responders: ["ryoai_bot"],
9621
+ },
9622
+ };
9623
+ },
9624
+ performLocalBotDelivery: async () => ({
9625
+ delivery: { dryRun: true, body: {} },
9626
+ archive: {},
9627
+ }),
9628
+ serializeRunnerTriggerPolicy: (value) => value,
9629
+ serializeRunnerArchivePolicy: (value) => value,
9630
+ buildRunnerExecutionDeps: () => ({}),
9631
+ buildRunnerDeliveryDeps: () => ({}),
9632
+ buildRunnerRuntimeDeps: () => ({}),
9633
+ resolveConversationPeerBots: () => [
9634
+ { id: "bot-lead-1", name: "RyoAI_bot" },
9635
+ { id: "bot-peer-1", name: "RyoAI2_bot" },
9636
+ { id: "bot-peer-2", name: "RyoAI3_bot" },
9637
+ ],
9638
+ },
9639
+ });
9640
+ push(
9641
+ "delegated_single_lead_non_lead_summary_request_allowed",
9642
+ processed.kind === "replied"
9643
+ && aiCalls === 1
9644
+ && String(processed.result?.execution_contract_type || "") === "summary_request",
9645
+ `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9646
+ );
9647
+ } catch (err) {
9648
+ push("delegated_single_lead_non_lead_summary_request_allowed", false, String(err?.message || err));
9649
+ }
9650
+
9651
+ try {
9652
+ let aiCalls = 0;
9653
+ const processed = await processRunnerSelectedRecord({
9654
+ routeKey: "delegated-single-lead-non-lead-new-assignment-blocked-key",
9655
+ normalizedRoute: normalizeRunnerRoute({
9656
+ name: "telegram-monitor-delegated-single-lead-non-lead-new-assignment-blocked",
9657
+ project_id: selftestProjectID,
9658
+ provider: "telegram",
9659
+ role: "monitor",
9660
+ role_profile: "monitor",
9661
+ destination_id: "dest-1",
9662
+ destination_label: "Main Room",
9663
+ server_bot_name: "RyoAI3_bot",
9664
+ server_bot_id: "bot-peer-2",
9665
+ trigger_policy: {
9666
+ mentions_only: true,
9667
+ direct_messages: true,
9668
+ reply_to_bot_messages: true,
9669
+ },
9670
+ archive_policy: {
9671
+ mirror_replies: true,
9672
+ dedupe_inbound: true,
9673
+ dedupe_outbound: true,
9674
+ skip_bot_messages: true,
9675
+ },
9676
+ dry_run_delivery: true,
9677
+ }),
9678
+ selectedRecord: {
9679
+ id: "comment-delegated-single-lead-non-lead-new-assignment-blocked",
9680
+ createdAt: "2026-03-16T00:02:22.000Z",
9681
+ parsedArchive: {
9682
+ kind: "bot_reply",
9683
+ conversationID: "comment-delegated-single-lead-open",
9684
+ conversationMode: "public_multi_bot",
9685
+ conversationStage: "bot_reply",
9686
+ conversationIntentMode: "delegated_single_lead",
9687
+ conversationAllowBotToBot: true,
9688
+ conversationLeadBotUsername: "ryoai_bot",
9689
+ conversationParticipants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9690
+ conversationInitialResponders: ["ryoai_bot"],
9691
+ conversationAllowedResponders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9692
+ conversationSummaryBotUsername: "ryoai_bot",
9693
+ botUsername: "RyoAI_bot",
9694
+ botName: "RyoAI_bot",
9695
+ sender: "RyoAI_bot",
9696
+ body: "@RyoAI3_bot Review the biggest implementation risk.",
9697
+ mentionUsernames: ["ryoai3_bot"],
9698
+ executionContract: {
9699
+ type: "delegation",
9700
+ actionable: true,
9701
+ assignments: [
9702
+ { target_bot: "ryoai3_bot", task: "Review the biggest implementation risk.", mode: "conversation_contribution" },
9703
+ ],
9704
+ summary_bot: "ryoai_bot",
9705
+ next_responders: ["ryoai3_bot"],
9706
+ },
9707
+ },
9708
+ },
9709
+ pendingOrdered: [],
9710
+ bot: {
9711
+ id: "bot-peer-2",
9712
+ name: "RyoAI3_bot",
9713
+ username: "RyoAI3_bot",
9714
+ role: "monitor",
9715
+ provider: "telegram",
9716
+ },
9717
+ destination: {
9718
+ id: "dest-1",
9719
+ label: "Main Room",
9720
+ provider: "telegram",
9721
+ chatID: "-100123",
9722
+ },
9723
+ archiveThread: {
9724
+ threadID: "thread-1",
9725
+ workItemID: "work-item-1",
9726
+ },
9727
+ executionPlan: {
9728
+ mode: "role_profile",
9729
+ roleProfileName: "monitor",
9730
+ roleProfile: {
9731
+ client: "sample",
9732
+ model: "",
9733
+ permissionMode: "read_only",
9734
+ reasoningEffort: "low",
9735
+ },
9736
+ workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-non-lead-new-assignment-blocked"),
9737
+ workspaceSource: "selftest",
9738
+ usedCommandFallback: false,
9739
+ },
9740
+ runtime: {
9741
+ baseURL: "https://example.test",
9742
+ token: "selftest-token",
9743
+ timeoutSeconds: 30,
9744
+ actor: { user_id: "user-1" },
9745
+ },
9746
+ deps: {
9747
+ saveRunnerRouteState: () => {},
9748
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
9749
+ runRunnerAIExecution: async () => {
9750
+ aiCalls += 1;
9751
+ return {
9752
+ skip: false,
9753
+ reply: "@RyoAI2_bot 추가 검토를 해주세요.",
9754
+ contract: {
9755
+ type: "delegation",
9756
+ actionable: true,
9757
+ assignments: [
9758
+ { target_bot: "ryoai2_bot", task: "추가 검토를 해주세요.", mode: "conversation_contribution" },
9759
+ ],
9760
+ next_responders: ["ryoai2_bot"],
9761
+ },
9762
+ };
9763
+ },
9764
+ performLocalBotDelivery: async () => ({
9765
+ delivery: { dryRun: true, body: {} },
9766
+ archive: {},
9767
+ }),
9768
+ serializeRunnerTriggerPolicy: (value) => value,
9769
+ serializeRunnerArchivePolicy: (value) => value,
9770
+ buildRunnerExecutionDeps: () => ({}),
9771
+ buildRunnerDeliveryDeps: () => ({}),
9772
+ buildRunnerRuntimeDeps: () => ({}),
9773
+ resolveConversationPeerBots: () => [
9774
+ { id: "bot-lead-1", name: "RyoAI_bot" },
9775
+ { id: "bot-peer-1", name: "RyoAI2_bot" },
9776
+ { id: "bot-peer-2", name: "RyoAI3_bot" },
9777
+ ],
9778
+ },
9779
+ });
9780
+ push(
9781
+ "delegated_single_lead_non_lead_new_assignment_blocked",
9782
+ processed.kind === "skipped"
9783
+ && aiCalls === 1
9784
+ && String(processed.result?.response_contract_validation_status || "") === "non_lead_public_assignment",
9785
+ `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} validation=${String(processed.result?.response_contract_validation_status || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9786
+ );
9787
+ } catch (err) {
9788
+ push("delegated_single_lead_non_lead_new_assignment_blocked", false, String(err?.message || err));
9789
+ }
9790
+
9791
+ try {
9792
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-artifacts-ok-"));
9514
9793
  const artifactPath = path.join(workspaceDir, "docs", "plan.md");
9515
9794
  fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
9516
9795
  fs.writeFileSync(artifactPath, "# plan\n", "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.228",
3
+ "version": "0.2.230",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [