metheus-governance-mcp-cli 0.2.159 → 0.2.161

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/README.md CHANGED
@@ -516,6 +516,8 @@ Workspace/source-of-truth model:
516
516
  - `ctxpack` is project guidance/specification content stored inside the workspace and on the server; it is not the local workspace authority
517
517
  - `.metheus/ctxpack-cache/` is local synced cache for ctxpack content; do not treat it as the final authoring target for project documents
518
518
  - `.metheus/runner-runtime/local-ai-scratch/` is local runner scratch space for AI CLI runtime support; do not treat it as a project artifact location or a user-facing document location
519
+ - `.metheus_ctxpack_sync.json` is sync metadata only; do not treat it as an official ctxpack source document
520
+ - Official ctxpack authoring may live under normal project files or `.metheus/...` authoring paths, but exclude cache/runtime/metadata paths such as `.metheus/ctxpack-cache/`, `.metheus/runner-runtime/`, and `.metheus_ctxpack_sync.json`
519
521
 
520
522
  Role profile fields:
521
523
  - `client`: `gpt`, `claude`, `gemini`, or `sample`
@@ -664,6 +666,9 @@ metheus-governance-mcp-cli ctxpack pull --project-id <project_uuid> --base-url h
664
666
  Canonical ctxpack write tool name:
665
667
  - use `ctxpack.update`
666
668
  - do not use legacy names like `ctxpack.push` or `ctxpack.save`
669
+ - chat/runner guidance:
670
+ - use `ctxpack.update` for official project guidance/instruction/policy/spec requests
671
+ - do not force `ctxpack.update` for normal README or generic implementation-document requests unless the request explicitly asks for official project guidance
667
672
 
668
673
  When `workitem.list` returns empty, proxy appends a hint to call `project.summary` first.
669
674
 
@@ -1499,7 +1499,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1499
1499
  "Do the work for this step now. Do not say you will start later, wait, come back later, or plan first.",
1500
1500
  "If this is a worker step, actually create/update the required project artifacts now and include them in artifacts.",
1501
1501
  executionStep.ctxpack_update_required === true || executionStep.ctxpackUpdateRequired === true
1502
- ? "This step must update project guidance/specification content that belongs in ctxpack-backed workspace source files. Return every official guide/instruction/spec file in ctxpack_files with path, content, and doc_type so the runner can materialize those files and update server ctxpack. Prefer official source paths under .metheus/project-guides/, .metheus/rules/, or other explicit workspace authoring paths when you are writing project guidance or instructions. Do not treat .metheus/ctxpack-cache, .metheus/runner-runtime/local-ai-scratch, .metheus_ctxpack_sync.json, or .claude/ as final authoring targets."
1502
+ ? "This step must update project guidance/specification content that belongs in ctxpack-backed workspace source files. Return every official guide/instruction/spec file in ctxpack_files with path, content, and doc_type so the runner can materialize those files and update server ctxpack. A ctxpack update step is incomplete if ctxpack_files is empty. Prefer official source paths under .metheus/project-guides/, .metheus/rules/, or other explicit workspace authoring paths when you are writing project guidance or instructions. Do not treat .metheus/ctxpack-cache, .metheus/runner-runtime/local-ai-scratch, .metheus_ctxpack_sync.json, or .claude/ as final authoring targets."
1503
1503
  : "",
1504
1504
  executionStep.work_items_required === true || executionStep.workItemsRequired === true
1505
1505
  ? "This step must also break the work into executable governance work items. Return them in work_items with atomic titles and useful descriptions."
@@ -1559,7 +1559,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1559
1559
  "- Do not claim that a file, plan, document, or code change is complete unless the corresponding artifact path is present in artifacts.",
1560
1560
  "",
1561
1561
  isInternalExecutionStep
1562
- ? "Return JSON only in one line: {\"reply\":\"what was completed in this step\",\"artifacts\":[{\"path\":\"relative/or/absolute/path\",\"kind\":\"plan|code|doc|spec|test\",\"operation\":\"create|update|delete\"}],\"ctxpack_files\":[{\"path\":\"relative/path.md\",\"content\":\"full document text\",\"doc_type\":\"guide|readme|agenda|rule|architecture|manifest\",\"operation\":\"create|update|delete\"}],\"work_items\":[{\"title\":\"short atomic task\",\"description\":\"useful implementation detail\"}],\"contract\":{\"type\":\"direct_result|summary_request|final_summary\",\"actionable\":true,\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}. Use ctxpack_files when ctxpack-backed guidance/instruction files must be authored. Use artifacts: [] only if this step truly changes no project files, and use work_items: [] only if this step truly creates no governance tasks."
1562
+ ? "Return JSON only in one line: {\"reply\":\"what was completed in this step\",\"artifacts\":[{\"path\":\"relative/or/absolute/path\",\"kind\":\"plan|code|doc|spec|test\",\"operation\":\"create|update|delete\"}],\"ctxpack_files\":[{\"path\":\"relative/path.md\",\"content\":\"full document text\",\"doc_type\":\"guide|readme|agenda|rule|architecture|manifest\",\"operation\":\"create|update|delete\"}],\"work_items\":[{\"title\":\"short atomic task\",\"description\":\"useful implementation detail\"}],\"contract\":{\"type\":\"direct_result|summary_request|final_summary\",\"actionable\":true,\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}. Use ctxpack_files when ctxpack-backed guidance/instruction files must be authored. If execution_step.ctxpack_update_required is true, ctxpack_files must not be empty. Use artifacts: [] only if this step truly changes no project files, and use work_items: [] only if this step truly creates no governance tasks."
1563
1563
  : responseContract.must_reply === true
1564
1564
  ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\"}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}."
1565
1565
  : terse
@@ -174,6 +174,21 @@ function buildExecutionFailureReplyText(detail) {
174
174
  if (/ctxpack\.update requires project ctxpack write access|ctxpack write access|ctxpack update permission|forbidden/i.test(normalizedDetail) && /ctxpack/i.test(normalizedDetail)) {
175
175
  return "I could not complete this request because the authenticated actor does not have ctxpack update permission for this project.";
176
176
  }
177
+ if (/ctxpack version_id is missing/i.test(normalizedDetail)) {
178
+ return "I could not complete this request because the project ctxpack version is missing. Refresh the project summary or ctxpack first.";
179
+ }
180
+ if (/ctxpack source path is not an official project authoring target|ctxpack source path is outside workspace|ctxpack source file \".*\" is missing content|ctxpack_files entry is missing path|ctxpack update step returned no ctxpack_files|did not produce any official ctxpack source files|ctxpack update produced no pushable ctxpack files|ctxpack\.update requires at least one validated official ctxpack source file/i.test(normalizedDetail)) {
181
+ return "I could not complete this request because the ctxpack update did not produce valid official ctxpack source files inside the workspace.";
182
+ }
183
+ if (/ctxpack source artifact is missing from workspace/i.test(normalizedDetail)) {
184
+ return "I could not complete this request because the ctxpack source files were not present in the workspace when the update ran.";
185
+ }
186
+ if (/invalid json response from ctxpack update/i.test(normalizedDetail)) {
187
+ return "I could not complete this request because the server returned an invalid response for the ctxpack update.";
188
+ }
189
+ if (/ctxpack/i.test(normalizedDetail) && /ECONNABORTED|ECONNRESET|ETIMEDOUT|http timeout|socket hang up|aborted/i.test(normalizedDetail)) {
190
+ return "I could not complete this request because the server ctxpack update request did not complete.";
191
+ }
177
192
  if (/ctxpack/i.test(normalizedDetail)) {
178
193
  return "I could not complete this request because the required ctxpack update did not succeed.";
179
194
  }
@@ -875,6 +890,18 @@ async function pushExecutionStepCtxpack({
875
890
  errors: ["runner execution dependency \"replaceProjectCtxpackFiles\" is required for ctxpack update steps"],
876
891
  };
877
892
  }
893
+ const pushableArtifacts = ensureArray(validatedArtifacts)
894
+ .filter((item) => {
895
+ const artifact = safeObject(item);
896
+ const artifactPath = String(artifact.relativePath || artifact.path || "").trim();
897
+ return artifactPath && !isDisallowedCtxpackSourceRelativePath(artifactPath);
898
+ });
899
+ if (!pushableArtifacts.length) {
900
+ return {
901
+ ctxpack: null,
902
+ errors: ["ctxpack.update requires at least one validated official ctxpack source file inside the workspace"],
903
+ };
904
+ }
878
905
  try {
879
906
  const response = await replaceProjectCtxpackFiles({
880
907
  siteBaseURL: runtime.baseURL,
@@ -883,7 +910,7 @@ async function pushExecutionStepCtxpack({
883
910
  actorUserID: safeObject(runtime.actor).user_id,
884
911
  projectID: normalizedRoute.projectID,
885
912
  workspaceDir: String(runtime.workspaceDir || "").trim() || String(normalizedRoute.workspaceDir || "").trim(),
886
- artifacts: validatedArtifacts,
913
+ artifacts: pushableArtifacts,
887
914
  });
888
915
  return {
889
916
  ctxpack: {
@@ -907,6 +934,36 @@ async function pushExecutionStepCtxpack({
907
934
  ],
908
935
  };
909
936
  }
937
+ if (/ctxpack version_id is missing/i.test(detail)) {
938
+ return {
939
+ ctxpack: null,
940
+ errors: ["ctxpack version_id is missing; run ctxpack pull or refresh project summary first"],
941
+ };
942
+ }
943
+ if (/ctxpack update produced no pushable ctxpack files/i.test(detail)) {
944
+ return {
945
+ ctxpack: null,
946
+ errors: ["ctxpack update produced no pushable ctxpack source files"],
947
+ };
948
+ }
949
+ if (/ctxpack source artifact is missing from workspace/i.test(detail)) {
950
+ return {
951
+ ctxpack: null,
952
+ errors: [detail],
953
+ };
954
+ }
955
+ if (/invalid json response from ctxpack update/i.test(detail)) {
956
+ return {
957
+ ctxpack: null,
958
+ errors: ["server ctxpack update returned an invalid JSON response"],
959
+ };
960
+ }
961
+ if (/ECONNABORTED|ECONNRESET|ETIMEDOUT|http timeout|socket hang up|aborted/i.test(detail)) {
962
+ return {
963
+ ctxpack: null,
964
+ errors: [`ctxpack.update server request aborted before completion: ${detail}`],
965
+ };
966
+ }
910
967
  return {
911
968
  ctxpack: null,
912
969
  errors: [`failed to update project ctxpack for step "${String(safeObject(step).goal || safeObject(step).role || "ctxpack update").trim()}": ${detail}`],
@@ -1573,6 +1630,14 @@ async function maybeExecuteDynamicRolePlan({
1573
1630
  ) {
1574
1631
  stepErrors.push(`${String(step.role || "").trim()} step only produced internal runtime scratch files; create official workspace files for ctxpack source material`);
1575
1632
  }
1633
+ if (safeObject(step).ctxpackUpdateRequired === true) {
1634
+ if (ensureArray(stepResult?.ctxpackFiles).length === 0) {
1635
+ stepErrors.push(`${String(step.role || "").trim()} step returned no ctxpack_files for a required ctxpack update`);
1636
+ }
1637
+ if (ensureArray(ctxpackMaterialization.artifacts).length === 0) {
1638
+ stepErrors.push(`${String(step.role || "").trim()} step did not produce any official ctxpack source files inside the workspace`);
1639
+ }
1640
+ }
1576
1641
  if (requiresArtifactsForExecutionStep(step) && ensureArray(stepValidation.artifacts).length === 0) {
1577
1642
  if (reportedArtifactPaths.length > 0) {
1578
1643
  stepErrors.push(`${String(step.role || "").trim()} step reported project artifacts that were not observed in the workspace`);
@@ -4137,6 +4137,13 @@ export async function runSelftestRunnerScenarios(push, deps) {
4137
4137
  skip: false,
4138
4138
  reply: "Created the implementation plan and broke the work into governance tasks.",
4139
4139
  artifacts: [{ path: "doc/implementation-plan.md", kind: "plan", operation: "create" }],
4140
+ ctxpackFiles: [
4141
+ {
4142
+ path: "doc/implementation-plan.md",
4143
+ docType: "guide",
4144
+ content: "# Implementation Plan\n",
4145
+ },
4146
+ ],
4140
4147
  workItems: [
4141
4148
  { title: "Create API specification", description: "Draft the first API contract from the implementation plan." },
4142
4149
  { title: "Implement onboarding flow", description: "Build the first user onboarding screens and state handling." },
@@ -4575,6 +4582,13 @@ export async function runSelftestRunnerScenarios(push, deps) {
4575
4582
  skip: false,
4576
4583
  reply: "Updated the implementation guide in the workspace.",
4577
4584
  artifacts: [{ path: "docs/implementation-guide.md", kind: "doc", operation: "update" }],
4585
+ ctxpackFiles: [
4586
+ {
4587
+ path: "docs/implementation-guide.md",
4588
+ docType: "guide",
4589
+ content: "# Implementation Guide\n",
4590
+ },
4591
+ ],
4578
4592
  workItems: [],
4579
4593
  };
4580
4594
  }
@@ -4987,6 +5001,13 @@ export async function runSelftestRunnerScenarios(push, deps) {
4987
5001
  skip: false,
4988
5002
  reply: "Updated the implementation guide in the workspace.",
4989
5003
  artifacts: [{ path: "docs/implementation-guide.md", kind: "doc", operation: "update" }],
5004
+ ctxpackFiles: [
5005
+ {
5006
+ path: "docs/implementation-guide.md",
5007
+ docType: "guide",
5008
+ content: "# Implementation Guide\n",
5009
+ },
5010
+ ],
4990
5011
  workItems: [],
4991
5012
  };
4992
5013
  }
@@ -5081,13 +5102,13 @@ export async function runSelftestRunnerScenarios(push, deps) {
5081
5102
  let deliveryCalls = 0;
5082
5103
  let deliveredText = "";
5083
5104
  let ctxpackUpdateCalls = 0;
5084
- const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-ctxpack-guidance-force-"));
5105
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-ctxpack-requires-files-"));
5085
5106
  const expectedPath = ".metheus/project-guides/project-operating-guide.md";
5086
5107
  const expectedAbsolutePath = path.join(workspaceDir, ".metheus", "project-guides", "project-operating-guide.md");
5087
5108
  const processed = await processRunnerSelectedRecord({
5088
- routeKey: "single-bot-ctxpack-guidance-force-key",
5109
+ routeKey: "single-bot-ctxpack-requires-files-key",
5089
5110
  normalizedRoute: normalizeRunnerRoute({
5090
- name: "telegram-monitor-single-bot-ctxpack-guidance-force",
5111
+ name: "telegram-monitor-single-bot-ctxpack-requires-files",
5091
5112
  project_id: selftestProjectID,
5092
5113
  provider: "telegram",
5093
5114
  role: "monitor",
@@ -5110,16 +5131,16 @@ export async function runSelftestRunnerScenarios(push, deps) {
5110
5131
  dry_run_delivery: true,
5111
5132
  }),
5112
5133
  selectedRecord: {
5113
- id: "comment-single-bot-ctxpack-guidance-force",
5114
- createdAt: "2026-03-17T01:00:05.750Z",
5134
+ id: "comment-single-bot-ctxpack-requires-files",
5135
+ createdAt: "2026-03-17T01:00:05.625Z",
5115
5136
  parsedArchive: {
5116
5137
  kind: "telegram_message",
5117
5138
  chatID: "-100123",
5118
5139
  chatType: "supergroup",
5119
5140
  senderIsBot: false,
5120
- body: "@RyoAI_bot 프로젝트 운영 가이드를 작성해서 ctxpack에 반영해줘.",
5141
+ body: "@RyoAI_bot update the operating guide in ctxpack.",
5121
5142
  mentionUsernames: ["RyoAI_bot"],
5122
- messageID: 1310,
5143
+ messageID: 13095,
5123
5144
  },
5124
5145
  },
5125
5146
  pendingOrdered: [],
@@ -5167,7 +5188,197 @@ export async function runSelftestRunnerScenarios(push, deps) {
5167
5188
  if (stepRole === "monitor") {
5168
5189
  return {
5169
5190
  skip: false,
5170
- reply: "Identified this as an official project guidance update request.",
5191
+ reply: "Classified this as a ctxpack guidance update request.",
5192
+ };
5193
+ }
5194
+ if (stepRole === "worker") {
5195
+ fs.mkdirSync(path.dirname(expectedAbsolutePath), { recursive: true });
5196
+ fs.writeFileSync(expectedAbsolutePath, "# Project Operating Guide\n", "utf8");
5197
+ return {
5198
+ skip: false,
5199
+ reply: "Updated the guide file.",
5200
+ artifacts: [{ path: expectedPath, kind: "doc", operation: "update" }],
5201
+ workItems: [],
5202
+ };
5203
+ }
5204
+ throw new Error(`unexpected step role ${stepRole}`);
5205
+ },
5206
+ performLocalBotDelivery: async ({ text }) => {
5207
+ deliveryCalls += 1;
5208
+ deliveredText = String(text || "");
5209
+ return {
5210
+ delivery: { dryRun: true, body: {} },
5211
+ archive: {},
5212
+ };
5213
+ },
5214
+ serializeRunnerTriggerPolicy: (value) => value,
5215
+ serializeRunnerArchivePolicy: (value) => value,
5216
+ buildRunnerExecutionDeps: () => ({
5217
+ validateWorkspaceArtifacts,
5218
+ analyzeHumanConversationIntentWithAI: async () => ({
5219
+ mode: "single_bot",
5220
+ lead_bot: "ryoai_bot",
5221
+ participants: ["ryoai_bot"],
5222
+ initial_responders: ["ryoai_bot"],
5223
+ allowed_responders: ["ryoai_bot"],
5224
+ summary_bot: "",
5225
+ allow_bot_to_bot: false,
5226
+ reply_expectation: "actionable",
5227
+ }),
5228
+ planRoleExecutionWithAI: async () => ({
5229
+ requiresExecution: true,
5230
+ summaryRole: "worker",
5231
+ steps: [
5232
+ { role: "monitor", goal: "Inspect the guidance request" },
5233
+ {
5234
+ role: "worker",
5235
+ goal: "Update the operating guide in ctxpack",
5236
+ artifactsRequired: true,
5237
+ ctxpackUpdateRequired: true,
5238
+ outputHint: expectedPath,
5239
+ },
5240
+ ],
5241
+ }),
5242
+ auditRoleExecutionPlanWithAI: async () => ({
5243
+ requires_execution: true,
5244
+ requires_worker_step: true,
5245
+ requires_ctxpack_update_step: true,
5246
+ plan_satisfies_request: true,
5247
+ reason: "worker ctxpack step present",
5248
+ }),
5249
+ resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
5250
+ mode: "role_profile",
5251
+ roleProfileName: String(roleName || "").trim(),
5252
+ roleProfile: {
5253
+ client: "sample",
5254
+ model: "",
5255
+ permissionMode: String(roleName || "").trim() === "worker" ? "workspace_write" : "read_only",
5256
+ reasoningEffort: String(roleName || "").trim() === "worker" ? "medium" : "low",
5257
+ },
5258
+ workspaceDir,
5259
+ workspaceSource: "selftest",
5260
+ usedCommandFallback: false,
5261
+ }),
5262
+ replaceProjectCtxpackFiles: async () => {
5263
+ ctxpackUpdateCalls += 1;
5264
+ throw new Error("ctxpack.update should not run when ctxpack_files are missing");
5265
+ },
5266
+ loadBotRunnerConfig: () => ({
5267
+ roleProfiles: {},
5268
+ botBindings: {},
5269
+ routes: [],
5270
+ projectMappings: {},
5271
+ }),
5272
+ }),
5273
+ buildRunnerDeliveryDeps: () => ({}),
5274
+ buildRunnerRuntimeDeps: () => ({}),
5275
+ resolveConversationPeerBots: () => [
5276
+ { id: "bot-lead-1", name: "RyoAI_bot" },
5277
+ ],
5278
+ },
5279
+ });
5280
+ push(
5281
+ "single_bot_ctxpack_update_requires_ctxpack_files",
5282
+ processed.kind === "error"
5283
+ && String(processed.result?.outcome || "") === "execution_failed"
5284
+ && ctxpackUpdateCalls === 0
5285
+ && deliveryCalls === 1
5286
+ && /valid official ctxpack source files/i.test(deliveredText),
5287
+ `kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} ctxpack_updates=${ctxpackUpdateCalls} delivery_calls=${deliveryCalls} delivered=${deliveredText} detail=${String(processed.result?.detail || "(none)")}`,
5288
+ );
5289
+ } catch (err) {
5290
+ push("single_bot_ctxpack_update_requires_ctxpack_files", false, String(err?.message || err));
5291
+ }
5292
+
5293
+ try {
5294
+ let deliveryCalls = 0;
5295
+ let deliveredText = "";
5296
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-ctxpack-version-missing-"));
5297
+ const expectedPath = ".metheus/project-guides/project-operating-guide.md";
5298
+ const processed = await processRunnerSelectedRecord({
5299
+ routeKey: "single-bot-ctxpack-version-missing-key",
5300
+ normalizedRoute: normalizeRunnerRoute({
5301
+ name: "telegram-monitor-single-bot-ctxpack-version-missing",
5302
+ project_id: selftestProjectID,
5303
+ provider: "telegram",
5304
+ role: "monitor",
5305
+ role_profile: "monitor",
5306
+ destination_id: "dest-1",
5307
+ destination_label: "Main Room",
5308
+ server_bot_name: "RyoAI_bot",
5309
+ server_bot_id: "bot-lead-1",
5310
+ trigger_policy: {
5311
+ mentions_only: true,
5312
+ direct_messages: true,
5313
+ reply_to_bot_messages: true,
5314
+ },
5315
+ archive_policy: {
5316
+ mirror_replies: true,
5317
+ dedupe_inbound: true,
5318
+ dedupe_outbound: true,
5319
+ skip_bot_messages: true,
5320
+ },
5321
+ dry_run_delivery: true,
5322
+ }),
5323
+ selectedRecord: {
5324
+ id: "comment-single-bot-ctxpack-version-missing",
5325
+ createdAt: "2026-03-17T01:00:05.675Z",
5326
+ parsedArchive: {
5327
+ kind: "telegram_message",
5328
+ chatID: "-100123",
5329
+ chatType: "supergroup",
5330
+ senderIsBot: false,
5331
+ body: "@RyoAI_bot update the operating guide in ctxpack.",
5332
+ mentionUsernames: ["RyoAI_bot"],
5333
+ messageID: 13096,
5334
+ },
5335
+ },
5336
+ pendingOrdered: [],
5337
+ bot: {
5338
+ id: "bot-lead-1",
5339
+ name: "RyoAI_bot",
5340
+ username: "RyoAI_bot",
5341
+ role: "monitor",
5342
+ provider: "telegram",
5343
+ },
5344
+ destination: {
5345
+ id: "dest-1",
5346
+ label: "Main Room",
5347
+ provider: "telegram",
5348
+ chatID: "-100123",
5349
+ },
5350
+ archiveThread: {
5351
+ threadID: "thread-1",
5352
+ workItemID: "work-item-1",
5353
+ },
5354
+ executionPlan: {
5355
+ mode: "role_profile",
5356
+ roleProfileName: "monitor",
5357
+ roleProfile: {
5358
+ client: "sample",
5359
+ model: "",
5360
+ permissionMode: "read_only",
5361
+ reasoningEffort: "low",
5362
+ },
5363
+ workspaceDir,
5364
+ workspaceSource: "selftest",
5365
+ usedCommandFallback: false,
5366
+ },
5367
+ runtime: {
5368
+ baseURL: "https://example.test",
5369
+ token: "selftest-token",
5370
+ timeoutSeconds: 30,
5371
+ actor: { user_id: "user-1" },
5372
+ },
5373
+ deps: {
5374
+ saveRunnerRouteState: () => {},
5375
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
5376
+ runRunnerAIExecution: async ({ route }) => {
5377
+ const stepRole = String(route?.role || "").trim().toLowerCase();
5378
+ if (stepRole === "monitor") {
5379
+ return {
5380
+ skip: false,
5381
+ reply: "Classified this as a ctxpack guidance update request.",
5171
5382
  };
5172
5383
  }
5173
5384
  if (stepRole === "worker") {
@@ -5179,7 +5390,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
5179
5390
  {
5180
5391
  path: expectedPath,
5181
5392
  docType: "guide",
5182
- content: "# Project Operating Guide\n\nUse ctxpack-backed official guidance files.\n",
5393
+ content: "# Project Operating Guide\n\nVersion check required.\n",
5183
5394
  },
5184
5395
  ],
5185
5396
  workItems: [],
@@ -5216,16 +5427,19 @@ export async function runSelftestRunnerScenarios(push, deps) {
5216
5427
  { role: "monitor", goal: "Inspect the guidance request" },
5217
5428
  {
5218
5429
  role: "worker",
5219
- goal: "Draft the project operating guide",
5430
+ goal: "Update the operating guide in ctxpack",
5220
5431
  artifactsRequired: true,
5432
+ ctxpackUpdateRequired: true,
5433
+ outputHint: expectedPath,
5221
5434
  },
5222
5435
  ],
5223
5436
  }),
5224
5437
  auditRoleExecutionPlanWithAI: async () => ({
5225
5438
  requires_execution: true,
5226
5439
  requires_worker_step: true,
5440
+ requires_ctxpack_update_step: true,
5227
5441
  plan_satisfies_request: true,
5228
- reason: "worker step present",
5442
+ reason: "worker ctxpack step present",
5229
5443
  }),
5230
5444
  resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
5231
5445
  mode: "role_profile",
@@ -5240,20 +5454,8 @@ export async function runSelftestRunnerScenarios(push, deps) {
5240
5454
  workspaceSource: "selftest",
5241
5455
  usedCommandFallback: false,
5242
5456
  }),
5243
- replaceProjectCtxpackFiles: async ({ artifacts }) => {
5244
- ctxpackUpdateCalls += 1;
5245
- return {
5246
- ctxpack_id: "ctxpack-3",
5247
- version_id: "version-ctxpack-3",
5248
- version: "0.3.0",
5249
- status: "draft",
5250
- files: ensureArray(artifacts).map((artifact) => ({
5251
- path: String(artifact.path || artifact.relativePath || "").trim(),
5252
- doc_type: "guide",
5253
- })),
5254
- pushed_file_count: ensureArray(artifacts).length,
5255
- changed_paths: ensureArray(artifacts).map((artifact) => String(artifact.path || artifact.relativePath || "").trim()),
5256
- };
5457
+ replaceProjectCtxpackFiles: async () => {
5458
+ throw new Error("ctxpack version_id is missing; run ctxpack pull or refresh project summary first");
5257
5459
  },
5258
5460
  loadBotRunnerConfig: () => ({
5259
5461
  roleProfiles: {},
@@ -5270,17 +5472,606 @@ export async function runSelftestRunnerScenarios(push, deps) {
5270
5472
  },
5271
5473
  });
5272
5474
  push(
5273
- "single_bot_guide_request_forces_ctxpack_update_when_planner_omits_it",
5274
- processed.kind === "replied"
5275
- && ctxpackUpdateCalls === 1
5276
- && fs.existsSync(expectedAbsolutePath)
5277
- && ensureArray(processed.result?.ctxpack_changed_paths).includes(expectedPath)
5475
+ "single_bot_ctxpack_update_reports_missing_version",
5476
+ processed.kind === "error"
5477
+ && String(processed.result?.outcome || "") === "execution_failed"
5278
5478
  && deliveryCalls === 1
5279
- && /operating guide/i.test(deliveredText),
5280
- `kind=${String(processed.kind || "(none)")} ctxpack_updates=${ctxpackUpdateCalls} exists=${String(fs.existsSync(expectedAbsolutePath))} changed=${ensureArray(processed.result?.ctxpack_changed_paths).join(",")} delivered=${deliveredText}`,
5479
+ && /ctxpack version is missing/i.test(deliveredText),
5480
+ `kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} delivery_calls=${deliveryCalls} delivered=${deliveredText} detail=${String(processed.result?.detail || "(none)")}`,
5281
5481
  );
5282
5482
  } catch (err) {
5283
- push("single_bot_guide_request_forces_ctxpack_update_when_planner_omits_it", false, String(err?.message || err));
5483
+ push("single_bot_ctxpack_update_reports_missing_version", false, String(err?.message || err));
5484
+ }
5485
+
5486
+ try {
5487
+ let deliveryCalls = 0;
5488
+ let deliveredText = "";
5489
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-ctxpack-server-abort-"));
5490
+ const expectedPath = ".metheus/project-guides/project-operating-guide.md";
5491
+ const processed = await processRunnerSelectedRecord({
5492
+ routeKey: "single-bot-ctxpack-server-abort-key",
5493
+ normalizedRoute: normalizeRunnerRoute({
5494
+ name: "telegram-monitor-single-bot-ctxpack-server-abort",
5495
+ project_id: selftestProjectID,
5496
+ provider: "telegram",
5497
+ role: "monitor",
5498
+ role_profile: "monitor",
5499
+ destination_id: "dest-1",
5500
+ destination_label: "Main Room",
5501
+ server_bot_name: "RyoAI_bot",
5502
+ server_bot_id: "bot-lead-1",
5503
+ trigger_policy: {
5504
+ mentions_only: true,
5505
+ direct_messages: true,
5506
+ reply_to_bot_messages: true,
5507
+ },
5508
+ archive_policy: {
5509
+ mirror_replies: true,
5510
+ dedupe_inbound: true,
5511
+ dedupe_outbound: true,
5512
+ skip_bot_messages: true,
5513
+ },
5514
+ dry_run_delivery: true,
5515
+ }),
5516
+ selectedRecord: {
5517
+ id: "comment-single-bot-ctxpack-server-abort",
5518
+ createdAt: "2026-03-17T01:00:05.700Z",
5519
+ parsedArchive: {
5520
+ kind: "telegram_message",
5521
+ chatID: "-100123",
5522
+ chatType: "supergroup",
5523
+ senderIsBot: false,
5524
+ body: "@RyoAI_bot update the operating guide in ctxpack.",
5525
+ mentionUsernames: ["RyoAI_bot"],
5526
+ messageID: 13097,
5527
+ },
5528
+ },
5529
+ pendingOrdered: [],
5530
+ bot: {
5531
+ id: "bot-lead-1",
5532
+ name: "RyoAI_bot",
5533
+ username: "RyoAI_bot",
5534
+ role: "monitor",
5535
+ provider: "telegram",
5536
+ },
5537
+ destination: {
5538
+ id: "dest-1",
5539
+ label: "Main Room",
5540
+ provider: "telegram",
5541
+ chatID: "-100123",
5542
+ },
5543
+ archiveThread: {
5544
+ threadID: "thread-1",
5545
+ workItemID: "work-item-1",
5546
+ },
5547
+ executionPlan: {
5548
+ mode: "role_profile",
5549
+ roleProfileName: "monitor",
5550
+ roleProfile: {
5551
+ client: "sample",
5552
+ model: "",
5553
+ permissionMode: "read_only",
5554
+ reasoningEffort: "low",
5555
+ },
5556
+ workspaceDir,
5557
+ workspaceSource: "selftest",
5558
+ usedCommandFallback: false,
5559
+ },
5560
+ runtime: {
5561
+ baseURL: "https://example.test",
5562
+ token: "selftest-token",
5563
+ timeoutSeconds: 30,
5564
+ actor: { user_id: "user-1" },
5565
+ },
5566
+ deps: {
5567
+ saveRunnerRouteState: () => {},
5568
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
5569
+ runRunnerAIExecution: async ({ route }) => {
5570
+ const stepRole = String(route?.role || "").trim().toLowerCase();
5571
+ if (stepRole === "monitor") {
5572
+ return {
5573
+ skip: false,
5574
+ reply: "Classified this as a ctxpack guidance update request.",
5575
+ };
5576
+ }
5577
+ if (stepRole === "worker") {
5578
+ return {
5579
+ skip: false,
5580
+ reply: "Prepared the official operating guide content.",
5581
+ artifacts: [],
5582
+ ctxpackFiles: [
5583
+ {
5584
+ path: expectedPath,
5585
+ docType: "guide",
5586
+ content: "# Project Operating Guide\n\nServer write must complete.\n",
5587
+ },
5588
+ ],
5589
+ workItems: [],
5590
+ };
5591
+ }
5592
+ throw new Error(`unexpected step role ${stepRole}`);
5593
+ },
5594
+ performLocalBotDelivery: async ({ text }) => {
5595
+ deliveryCalls += 1;
5596
+ deliveredText = String(text || "");
5597
+ return {
5598
+ delivery: { dryRun: true, body: {} },
5599
+ archive: {},
5600
+ };
5601
+ },
5602
+ serializeRunnerTriggerPolicy: (value) => value,
5603
+ serializeRunnerArchivePolicy: (value) => value,
5604
+ buildRunnerExecutionDeps: () => ({
5605
+ validateWorkspaceArtifacts,
5606
+ analyzeHumanConversationIntentWithAI: async () => ({
5607
+ mode: "single_bot",
5608
+ lead_bot: "ryoai_bot",
5609
+ participants: ["ryoai_bot"],
5610
+ initial_responders: ["ryoai_bot"],
5611
+ allowed_responders: ["ryoai_bot"],
5612
+ summary_bot: "",
5613
+ allow_bot_to_bot: false,
5614
+ reply_expectation: "actionable",
5615
+ }),
5616
+ planRoleExecutionWithAI: async () => ({
5617
+ requiresExecution: true,
5618
+ summaryRole: "worker",
5619
+ steps: [
5620
+ { role: "monitor", goal: "Inspect the guidance request" },
5621
+ {
5622
+ role: "worker",
5623
+ goal: "Update the operating guide in ctxpack",
5624
+ artifactsRequired: true,
5625
+ ctxpackUpdateRequired: true,
5626
+ outputHint: expectedPath,
5627
+ },
5628
+ ],
5629
+ }),
5630
+ auditRoleExecutionPlanWithAI: async () => ({
5631
+ requires_execution: true,
5632
+ requires_worker_step: true,
5633
+ requires_ctxpack_update_step: true,
5634
+ plan_satisfies_request: true,
5635
+ reason: "worker ctxpack step present",
5636
+ }),
5637
+ resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
5638
+ mode: "role_profile",
5639
+ roleProfileName: String(roleName || "").trim(),
5640
+ roleProfile: {
5641
+ client: "sample",
5642
+ model: "",
5643
+ permissionMode: String(roleName || "").trim() === "worker" ? "workspace_write" : "read_only",
5644
+ reasoningEffort: String(roleName || "").trim() === "worker" ? "medium" : "low",
5645
+ },
5646
+ workspaceDir,
5647
+ workspaceSource: "selftest",
5648
+ usedCommandFallback: false,
5649
+ }),
5650
+ replaceProjectCtxpackFiles: async () => {
5651
+ throw new Error("write ECONNABORTED");
5652
+ },
5653
+ loadBotRunnerConfig: () => ({
5654
+ roleProfiles: {},
5655
+ botBindings: {},
5656
+ routes: [],
5657
+ projectMappings: {},
5658
+ }),
5659
+ }),
5660
+ buildRunnerDeliveryDeps: () => ({}),
5661
+ buildRunnerRuntimeDeps: () => ({}),
5662
+ resolveConversationPeerBots: () => [
5663
+ { id: "bot-lead-1", name: "RyoAI_bot" },
5664
+ ],
5665
+ },
5666
+ });
5667
+ push(
5668
+ "single_bot_ctxpack_update_reports_server_abort",
5669
+ processed.kind === "error"
5670
+ && String(processed.result?.outcome || "") === "execution_failed"
5671
+ && deliveryCalls === 1
5672
+ && /server ctxpack update request did not complete/i.test(deliveredText),
5673
+ `kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} delivery_calls=${deliveryCalls} delivered=${deliveredText} detail=${String(processed.result?.detail || "(none)")}`,
5674
+ );
5675
+ } catch (err) {
5676
+ push("single_bot_ctxpack_update_reports_server_abort", false, String(err?.message || err));
5677
+ }
5678
+
5679
+ try {
5680
+ let deliveryCalls = 0;
5681
+ let deliveredText = "";
5682
+ let ctxpackUpdateCalls = 0;
5683
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-ctxpack-guidance-force-"));
5684
+ const expectedPath = ".metheus/project-guides/project-operating-guide.md";
5685
+ const expectedAbsolutePath = path.join(workspaceDir, ".metheus", "project-guides", "project-operating-guide.md");
5686
+ const processed = await processRunnerSelectedRecord({
5687
+ routeKey: "single-bot-ctxpack-guidance-force-key",
5688
+ normalizedRoute: normalizeRunnerRoute({
5689
+ name: "telegram-monitor-single-bot-ctxpack-guidance-force",
5690
+ project_id: selftestProjectID,
5691
+ provider: "telegram",
5692
+ role: "monitor",
5693
+ role_profile: "monitor",
5694
+ destination_id: "dest-1",
5695
+ destination_label: "Main Room",
5696
+ server_bot_name: "RyoAI_bot",
5697
+ server_bot_id: "bot-lead-1",
5698
+ trigger_policy: {
5699
+ mentions_only: true,
5700
+ direct_messages: true,
5701
+ reply_to_bot_messages: true,
5702
+ },
5703
+ archive_policy: {
5704
+ mirror_replies: true,
5705
+ dedupe_inbound: true,
5706
+ dedupe_outbound: true,
5707
+ skip_bot_messages: true,
5708
+ },
5709
+ dry_run_delivery: true,
5710
+ }),
5711
+ selectedRecord: {
5712
+ id: "comment-single-bot-ctxpack-guidance-force",
5713
+ createdAt: "2026-03-17T01:00:05.750Z",
5714
+ parsedArchive: {
5715
+ kind: "telegram_message",
5716
+ chatID: "-100123",
5717
+ chatType: "supergroup",
5718
+ senderIsBot: false,
5719
+ body: "@RyoAI_bot 프로젝트 운영 가이드를 작성해서 ctxpack에 반영해줘.",
5720
+ mentionUsernames: ["RyoAI_bot"],
5721
+ messageID: 1310,
5722
+ },
5723
+ },
5724
+ pendingOrdered: [],
5725
+ bot: {
5726
+ id: "bot-lead-1",
5727
+ name: "RyoAI_bot",
5728
+ username: "RyoAI_bot",
5729
+ role: "monitor",
5730
+ provider: "telegram",
5731
+ },
5732
+ destination: {
5733
+ id: "dest-1",
5734
+ label: "Main Room",
5735
+ provider: "telegram",
5736
+ chatID: "-100123",
5737
+ },
5738
+ archiveThread: {
5739
+ threadID: "thread-1",
5740
+ workItemID: "work-item-1",
5741
+ },
5742
+ executionPlan: {
5743
+ mode: "role_profile",
5744
+ roleProfileName: "monitor",
5745
+ roleProfile: {
5746
+ client: "sample",
5747
+ model: "",
5748
+ permissionMode: "read_only",
5749
+ reasoningEffort: "low",
5750
+ },
5751
+ workspaceDir,
5752
+ workspaceSource: "selftest",
5753
+ usedCommandFallback: false,
5754
+ },
5755
+ runtime: {
5756
+ baseURL: "https://example.test",
5757
+ token: "selftest-token",
5758
+ timeoutSeconds: 30,
5759
+ actor: { user_id: "user-1" },
5760
+ },
5761
+ deps: {
5762
+ saveRunnerRouteState: () => {},
5763
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
5764
+ runRunnerAIExecution: async ({ route }) => {
5765
+ const stepRole = String(route?.role || "").trim().toLowerCase();
5766
+ if (stepRole === "monitor") {
5767
+ return {
5768
+ skip: false,
5769
+ reply: "Identified this as an official project guidance update request.",
5770
+ };
5771
+ }
5772
+ if (stepRole === "worker") {
5773
+ return {
5774
+ skip: false,
5775
+ reply: "Prepared the official operating guide content.",
5776
+ artifacts: [],
5777
+ ctxpackFiles: [
5778
+ {
5779
+ path: expectedPath,
5780
+ docType: "guide",
5781
+ content: "# Project Operating Guide\n\nUse ctxpack-backed official guidance files.\n",
5782
+ },
5783
+ ],
5784
+ workItems: [],
5785
+ };
5786
+ }
5787
+ throw new Error(`unexpected step role ${stepRole}`);
5788
+ },
5789
+ performLocalBotDelivery: async ({ text }) => {
5790
+ deliveryCalls += 1;
5791
+ deliveredText = String(text || "");
5792
+ return {
5793
+ delivery: { dryRun: true, body: {} },
5794
+ archive: {},
5795
+ };
5796
+ },
5797
+ serializeRunnerTriggerPolicy: (value) => value,
5798
+ serializeRunnerArchivePolicy: (value) => value,
5799
+ buildRunnerExecutionDeps: () => ({
5800
+ validateWorkspaceArtifacts,
5801
+ analyzeHumanConversationIntentWithAI: async () => ({
5802
+ mode: "single_bot",
5803
+ lead_bot: "ryoai_bot",
5804
+ participants: ["ryoai_bot"],
5805
+ initial_responders: ["ryoai_bot"],
5806
+ allowed_responders: ["ryoai_bot"],
5807
+ summary_bot: "",
5808
+ allow_bot_to_bot: false,
5809
+ reply_expectation: "actionable",
5810
+ }),
5811
+ planRoleExecutionWithAI: async () => ({
5812
+ requiresExecution: true,
5813
+ summaryRole: "worker",
5814
+ steps: [
5815
+ { role: "monitor", goal: "Inspect the guidance request" },
5816
+ {
5817
+ role: "worker",
5818
+ goal: "Draft the project operating guide",
5819
+ artifactsRequired: true,
5820
+ },
5821
+ ],
5822
+ }),
5823
+ auditRoleExecutionPlanWithAI: async () => ({
5824
+ requires_execution: true,
5825
+ requires_worker_step: true,
5826
+ plan_satisfies_request: true,
5827
+ reason: "worker step present",
5828
+ }),
5829
+ resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
5830
+ mode: "role_profile",
5831
+ roleProfileName: String(roleName || "").trim(),
5832
+ roleProfile: {
5833
+ client: "sample",
5834
+ model: "",
5835
+ permissionMode: String(roleName || "").trim() === "worker" ? "workspace_write" : "read_only",
5836
+ reasoningEffort: String(roleName || "").trim() === "worker" ? "medium" : "low",
5837
+ },
5838
+ workspaceDir,
5839
+ workspaceSource: "selftest",
5840
+ usedCommandFallback: false,
5841
+ }),
5842
+ replaceProjectCtxpackFiles: async ({ artifacts }) => {
5843
+ ctxpackUpdateCalls += 1;
5844
+ return {
5845
+ ctxpack_id: "ctxpack-3",
5846
+ version_id: "version-ctxpack-3",
5847
+ version: "0.3.0",
5848
+ status: "draft",
5849
+ files: ensureArray(artifacts).map((artifact) => ({
5850
+ path: String(artifact.path || artifact.relativePath || "").trim(),
5851
+ doc_type: "guide",
5852
+ })),
5853
+ pushed_file_count: ensureArray(artifacts).length,
5854
+ changed_paths: ensureArray(artifacts).map((artifact) => String(artifact.path || artifact.relativePath || "").trim()),
5855
+ };
5856
+ },
5857
+ loadBotRunnerConfig: () => ({
5858
+ roleProfiles: {},
5859
+ botBindings: {},
5860
+ routes: [],
5861
+ projectMappings: {},
5862
+ }),
5863
+ }),
5864
+ buildRunnerDeliveryDeps: () => ({}),
5865
+ buildRunnerRuntimeDeps: () => ({}),
5866
+ resolveConversationPeerBots: () => [
5867
+ { id: "bot-lead-1", name: "RyoAI_bot" },
5868
+ ],
5869
+ },
5870
+ });
5871
+ push(
5872
+ "single_bot_guide_request_forces_ctxpack_update_when_planner_omits_it",
5873
+ processed.kind === "replied"
5874
+ && ctxpackUpdateCalls === 1
5875
+ && fs.existsSync(expectedAbsolutePath)
5876
+ && ensureArray(processed.result?.ctxpack_changed_paths).includes(expectedPath)
5877
+ && deliveryCalls === 1
5878
+ && /operating guide/i.test(deliveredText),
5879
+ `kind=${String(processed.kind || "(none)")} ctxpack_updates=${ctxpackUpdateCalls} exists=${String(fs.existsSync(expectedAbsolutePath))} changed=${ensureArray(processed.result?.ctxpack_changed_paths).join(",")} delivered=${deliveredText}`,
5880
+ );
5881
+ } catch (err) {
5882
+ push("single_bot_guide_request_forces_ctxpack_update_when_planner_omits_it", false, String(err?.message || err));
5883
+ }
5884
+
5885
+ try {
5886
+ let deliveryCalls = 0;
5887
+ let deliveredText = "";
5888
+ let ctxpackUpdateCalls = 0;
5889
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-readme-not-ctxpack-"));
5890
+ const readmePath = path.join(workspaceDir, "README.md");
5891
+ const processed = await processRunnerSelectedRecord({
5892
+ routeKey: "single-bot-readme-not-ctxpack-key",
5893
+ normalizedRoute: normalizeRunnerRoute({
5894
+ name: "telegram-monitor-single-bot-readme-not-ctxpack",
5895
+ project_id: selftestProjectID,
5896
+ provider: "telegram",
5897
+ role: "monitor",
5898
+ role_profile: "monitor",
5899
+ destination_id: "dest-1",
5900
+ destination_label: "Main Room",
5901
+ server_bot_name: "RyoAI_bot",
5902
+ server_bot_id: "bot-lead-1",
5903
+ trigger_policy: {
5904
+ mentions_only: true,
5905
+ direct_messages: true,
5906
+ reply_to_bot_messages: true,
5907
+ },
5908
+ archive_policy: {
5909
+ mirror_replies: true,
5910
+ dedupe_inbound: true,
5911
+ dedupe_outbound: true,
5912
+ skip_bot_messages: true,
5913
+ },
5914
+ dry_run_delivery: true,
5915
+ }),
5916
+ selectedRecord: {
5917
+ id: "comment-single-bot-readme-not-ctxpack",
5918
+ createdAt: "2026-03-17T01:00:05.875Z",
5919
+ parsedArchive: {
5920
+ kind: "telegram_message",
5921
+ chatID: "-100123",
5922
+ chatType: "supergroup",
5923
+ senderIsBot: false,
5924
+ body: "@RyoAI_bot create the implementation README now.",
5925
+ mentionUsernames: ["RyoAI_bot"],
5926
+ messageID: 1311,
5927
+ },
5928
+ },
5929
+ pendingOrdered: [],
5930
+ bot: {
5931
+ id: "bot-lead-1",
5932
+ name: "RyoAI_bot",
5933
+ username: "RyoAI_bot",
5934
+ role: "monitor",
5935
+ provider: "telegram",
5936
+ },
5937
+ destination: {
5938
+ id: "dest-1",
5939
+ label: "Main Room",
5940
+ provider: "telegram",
5941
+ chatID: "-100123",
5942
+ },
5943
+ archiveThread: {
5944
+ threadID: "thread-1",
5945
+ workItemID: "work-item-1",
5946
+ },
5947
+ executionPlan: {
5948
+ mode: "role_profile",
5949
+ roleProfileName: "monitor",
5950
+ roleProfile: {
5951
+ client: "sample",
5952
+ model: "",
5953
+ permissionMode: "read_only",
5954
+ reasoningEffort: "low",
5955
+ },
5956
+ workspaceDir,
5957
+ workspaceSource: "selftest",
5958
+ usedCommandFallback: false,
5959
+ },
5960
+ runtime: {
5961
+ baseURL: "https://example.test",
5962
+ token: "selftest-token",
5963
+ timeoutSeconds: 30,
5964
+ actor: { user_id: "user-1" },
5965
+ },
5966
+ deps: {
5967
+ saveRunnerRouteState: () => {},
5968
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
5969
+ runRunnerAIExecution: async ({ route }) => {
5970
+ const stepRole = String(route?.role || "").trim().toLowerCase();
5971
+ if (stepRole === "monitor") {
5972
+ return {
5973
+ skip: false,
5974
+ reply: "Identified this as a normal project README request.",
5975
+ };
5976
+ }
5977
+ if (stepRole === "worker") {
5978
+ fs.writeFileSync(readmePath, "# README\n", "utf8");
5979
+ return {
5980
+ skip: false,
5981
+ reply: "Created the implementation README.",
5982
+ artifacts: [
5983
+ { path: "README.md", kind: "document", operation: "create" },
5984
+ ],
5985
+ workItems: [],
5986
+ };
5987
+ }
5988
+ throw new Error(`unexpected step role ${stepRole}`);
5989
+ },
5990
+ performLocalBotDelivery: async ({ text }) => {
5991
+ deliveryCalls += 1;
5992
+ deliveredText = String(text || "");
5993
+ return {
5994
+ delivery: { dryRun: true, body: {} },
5995
+ archive: {},
5996
+ };
5997
+ },
5998
+ serializeRunnerTriggerPolicy: (value) => value,
5999
+ serializeRunnerArchivePolicy: (value) => value,
6000
+ buildRunnerExecutionDeps: () => ({
6001
+ validateWorkspaceArtifacts,
6002
+ analyzeHumanConversationIntentWithAI: async () => ({
6003
+ mode: "single_bot",
6004
+ lead_bot: "ryoai_bot",
6005
+ participants: ["ryoai_bot"],
6006
+ initial_responders: ["ryoai_bot"],
6007
+ allowed_responders: ["ryoai_bot"],
6008
+ summary_bot: "",
6009
+ allow_bot_to_bot: false,
6010
+ reply_expectation: "actionable",
6011
+ }),
6012
+ planRoleExecutionWithAI: async () => ({
6013
+ requiresExecution: true,
6014
+ summaryRole: "worker",
6015
+ steps: [
6016
+ { role: "monitor", goal: "Inspect the README request" },
6017
+ {
6018
+ role: "worker",
6019
+ goal: "Create the README",
6020
+ artifactsRequired: true,
6021
+ },
6022
+ ],
6023
+ }),
6024
+ auditRoleExecutionPlanWithAI: async () => ({
6025
+ requires_execution: true,
6026
+ requires_worker_step: true,
6027
+ requires_ctxpack_update_step: false,
6028
+ plan_satisfies_request: true,
6029
+ reason: "worker step present",
6030
+ }),
6031
+ resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
6032
+ mode: "role_profile",
6033
+ roleProfileName: String(roleName || "").trim(),
6034
+ roleProfile: {
6035
+ client: "sample",
6036
+ model: "",
6037
+ permissionMode: String(roleName || "").trim() === "worker" ? "workspace_write" : "read_only",
6038
+ reasoningEffort: String(roleName || "").trim() === "worker" ? "medium" : "low",
6039
+ },
6040
+ workspaceDir,
6041
+ workspaceSource: "selftest",
6042
+ usedCommandFallback: false,
6043
+ }),
6044
+ replaceProjectCtxpackFiles: async () => {
6045
+ ctxpackUpdateCalls += 1;
6046
+ throw new Error("ctxpack update should not run for a plain README request");
6047
+ },
6048
+ loadBotRunnerConfig: () => ({
6049
+ roleProfiles: {},
6050
+ botBindings: {},
6051
+ routes: [],
6052
+ projectMappings: {},
6053
+ }),
6054
+ }),
6055
+ buildRunnerDeliveryDeps: () => ({}),
6056
+ buildRunnerRuntimeDeps: () => ({}),
6057
+ resolveConversationPeerBots: () => [
6058
+ { id: "bot-lead-1", name: "RyoAI_bot" },
6059
+ ],
6060
+ },
6061
+ });
6062
+ push(
6063
+ "single_bot_readme_request_does_not_force_ctxpack_update",
6064
+ processed.kind === "replied"
6065
+ && ctxpackUpdateCalls === 0
6066
+ && fs.existsSync(readmePath)
6067
+ && ensureArray(processed.result?.artifact_paths).includes("README.md")
6068
+ && ensureArray(processed.result?.ctxpack_changed_paths).length === 0
6069
+ && deliveryCalls === 1
6070
+ && /README/i.test(deliveredText),
6071
+ `kind=${String(processed.kind || "(none)")} ctxpack_updates=${ctxpackUpdateCalls} exists=${String(fs.existsSync(readmePath))} artifacts=${ensureArray(processed.result?.artifact_paths).join(",")} changed=${ensureArray(processed.result?.ctxpack_changed_paths).join(",")} delivered=${deliveredText}`,
6072
+ );
6073
+ } catch (err) {
6074
+ push("single_bot_readme_request_does_not_force_ctxpack_update", false, String(err?.message || err));
5284
6075
  }
5285
6076
 
5286
6077
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.159",
3
+ "version": "0.2.161",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [