metheus-governance-mcp-cli 0.2.152 → 0.2.157

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
@@ -383,7 +383,7 @@ Checks:
383
383
  - smoke calls: `workitem.list`, `evidence.list`, `decision.list`
384
384
  - Telegram room audit for active project destinations, including external room bots and visible server/local mismatches
385
385
  - route-state workspace artifact boundary violations recorded by the runner
386
- - external project artifact findings discovered from known out-of-workspace roots such as `~/.claude/plans`
386
+ - external project artifact findings discovered from known out-of-workspace roots such as `~/.claude/plans` (Claude-managed area, not a Metheus authoring target)
387
387
 
388
388
  `--strict true` upgrades local runner route safety warnings into failures.
389
389
  Use it for production validation before enabling long-running bot routes.
@@ -497,8 +497,9 @@ Recommended production path:
497
497
  - keep project room identifier in server-side Chat Destinations
498
498
  - keep automation route config in `~/.metheus/bot-runner.json`
499
499
  - keep project workspace mappings in `~/.metheus/project-workspaces.json`
500
- - let `ctxpack pull` or project connection refresh the mapping automatically
501
- - successful `project.summary` / `project.describe` / `project.get` calls now also persist the trusted current workspace signal into the project workspace registry
500
+ - treat `project-workspaces.json` as the local workspace authority for this machine
501
+ - do not treat ctxpack sync/cache metadata as workspace authority
502
+ - do not assume `project.summary` / `project.describe` / `project.get` will rewrite local workspace mappings
502
503
  - keep per-role execution policy under `role_profiles`
503
504
  - keep Telegram-wide binding defaults in `~/.metheus/telegram-bots/global.env`
504
505
  - use `bot_bindings` in `bot-runner.json` only as local fallback/override
@@ -509,6 +510,13 @@ Why `workspace_dir` matters:
509
510
  - the local runner must pass the correct folder to Codex, Claude Code, or Gemini on that member's machine
510
511
  - without a valid project mapping, bots may answer with a generic local root instead of the real project workspace
511
512
 
513
+ Workspace/source-of-truth model:
514
+ - `project-workspaces.json` answers only one question: `project_id -> local workspace_dir on this machine`
515
+ - `bot-runner.json` stores executable route config and role profiles; it is not the project workspace registry
516
+ - `ctxpack` is project guidance/specification content stored inside the workspace and on the server; it is not the local workspace authority
517
+ - `.metheus/ctxpack-cache/` is local synced cache for ctxpack content; do not treat it as the final authoring target for project documents
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
+
512
520
  Role profile fields:
513
521
  - `client`: `gpt`, `claude`, `gemini`, or `sample`
514
522
  - `codex` is still accepted as a compatibility alias for `gpt`
@@ -564,8 +572,9 @@ Archive policy fields:
564
572
 
565
573
  Mapping behavior:
566
574
  - `ctxpack pull --workspace-dir <path>` stores `project_id -> workspace_dir`
567
- - proxy/project connection can also persist the mapping
568
- - broader project roots are preferred over nested tool folders when updating the mapping
575
+ - broader project roots are preferred over nested tool folders when updating the mapping intentionally
576
+ - `project.summary` / `project.describe` / `project.get` sync ctxpack cache, but they do not own local workspace authority
577
+ - if you need to change a project's local folder on this machine, update the project workspace registry intentionally instead of relying on ctxpack cache side effects
569
578
 
570
579
  Runner command contract:
571
580
  - stdin: JSON payload containing project, destination, trigger message, and recent context comments
@@ -633,6 +642,7 @@ These tools accept `project_id` and return:
633
642
  - workspace path -> auto-detected from client metadata/env by default
634
643
  - if workspace signal is missing in auto mode, sync is guarded (no local write)
635
644
  - use `--workspace-fallback-dir <path>` only when you intentionally want fallback writes
645
+ - this local cache sync does not make ctxpack the source of truth for `project-workspaces.json`
636
646
 
637
647
  Project-ID first-call rule:
638
648
  - when user gives only `Project ID`, call `project.summary` first.
@@ -651,9 +661,13 @@ Manual ctxpack pull/update:
651
661
  metheus-governance-mcp-cli ctxpack pull --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com
652
662
  ```
653
663
 
664
+ Canonical ctxpack write tool name:
665
+ - use `ctxpack.update`
666
+ - do not use legacy names like `ctxpack.push` or `ctxpack.save`
667
+
654
668
  When `workitem.list` returns empty, proxy appends a hint to call `project.summary` first.
655
669
 
656
- For `ctxpack` push conflicts (`ctxpack_conflict` / stale baseline), proxy behavior:
670
+ For `ctxpack.update` conflicts (`ctxpack_conflict` / stale baseline), proxy behavior:
657
671
  - standard conflict message with pull/merge/retry guidance
658
672
  - auto ctxpack pull-to-local on conflict by default (`--auto-pull-on-conflict=true`)
659
673
 
package/cli.mjs CHANGED
@@ -78,7 +78,7 @@ import {
78
78
  appendCtxpackEnsureSyncHints as appendCtxpackEnsureSyncHintsImpl,
79
79
  appendWorkitemListHints as appendWorkitemListHintsImpl,
80
80
  injectCtxpackPreflightToken as injectCtxpackPreflightTokenImpl,
81
- injectCtxpackPushDefaults as injectCtxpackPushDefaultsImpl,
81
+ injectCtxpackUpdateDefaults as injectCtxpackUpdateDefaultsImpl,
82
82
  maybeAutoSyncCtxpackForCall as maybeAutoSyncCtxpackForCallImpl,
83
83
  maybeAutoSyncCtxpackForSessionRequest as maybeAutoSyncCtxpackForSessionRequestImpl,
84
84
  stripLocalOnlyToolArgs as stripLocalOnlyToolArgsImpl,
@@ -212,7 +212,7 @@ const PROVIDER_ENV_ORDER = Object.keys(PROVIDER_ENV_CONFIG);
212
212
  const SELF_UPDATE_STATE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-cli-update.json");
213
213
  const CTXPACK_CACHE_RELATIVE_DIR = path.join(".metheus", "ctxpack-cache");
214
214
  const CTXPACK_META_FILENAME = ".metheus_ctxpack_sync.json";
215
- const CTXPACK_PUSH_TOOL_NAMES = ["ctxpack.push", "ctxpack.update", "ctxpack.save"];
215
+ const CTXPACK_UPDATE_TOOL_NAMES = ["ctxpack.update"];
216
216
  const CLI_META = loadCLIMeta();
217
217
  const CLI_NAME = CLI_META.name || "metheus-governance-mcp-cli";
218
218
  const CLI_VERSION = CLI_META.version || "0.0.0";
@@ -3129,7 +3129,10 @@ function shouldIgnoreCtxpackArtifactPath(relativePath) {
3129
3129
  if (!normalizedPath) return true;
3130
3130
  return (
3131
3131
  normalizedPath === CTXPACK_META_FILENAME.toLowerCase()
3132
- || normalizedPath.startsWith(".metheus/")
3132
+ || normalizedPath === ".metheus/ctxpack-cache"
3133
+ || normalizedPath.startsWith(".metheus/ctxpack-cache/")
3134
+ || normalizedPath === ".metheus/runner-runtime"
3135
+ || normalizedPath.startsWith(".metheus/runner-runtime/")
3133
3136
  || normalizedPath.startsWith(".claude/")
3134
3137
  || normalizedPath.startsWith(".git/")
3135
3138
  || normalizedPath.startsWith("node_modules/")
@@ -6568,18 +6571,18 @@ function buildLocalProjectDispatchDeps() {
6568
6571
  boolFromRaw,
6569
6572
  normalizeSiteBaseURL,
6570
6573
  loadProjectSummaryForTool: (params) => loadProjectSummaryForTool(params, buildProjectToolDeps()),
6571
- buildProjectSummaryText,
6572
- normalizeMergeStatusFilter,
6573
- loadCtxpackMergeBriefForTool: (params) => loadCtxpackMergeBriefForTool(params, buildProjectToolDeps()),
6574
- buildCtxpackMergeBriefText,
6575
- normalizeMergeAction,
6576
- executeCtxpackMergeActionForTool: (params) => executeCtxpackMergeActionForTool(params, buildProjectToolDeps()),
6577
- buildCtxpackMergeExecuteText,
6578
- rememberProjectWorkspaceMapping,
6579
- jsonRpcError,
6580
- jsonRpcResult,
6581
- localProjectToolNames: LOCAL_PROJECT_TOOL_NAMES,
6582
- localCtxpackMergeToolNames: LOCAL_CTXPACK_MERGE_TOOL_NAMES,
6574
+ buildProjectSummaryText,
6575
+ normalizeMergeStatusFilter,
6576
+ loadCtxpackMergeBriefForTool: (params) => loadCtxpackMergeBriefForTool(params, buildProjectToolDeps()),
6577
+ buildCtxpackMergeBriefText,
6578
+ normalizeMergeAction,
6579
+ executeCtxpackMergeActionForTool: (params) => executeCtxpackMergeActionForTool(params, buildProjectToolDeps()),
6580
+ buildCtxpackMergeExecuteText,
6581
+ rememberProjectWorkspaceMapping,
6582
+ jsonRpcError,
6583
+ jsonRpcResult,
6584
+ localProjectToolNames: LOCAL_PROJECT_TOOL_NAMES,
6585
+ localCtxpackMergeToolNames: LOCAL_CTXPACK_MERGE_TOOL_NAMES,
6583
6586
  };
6584
6587
  }
6585
6588
 
@@ -6601,7 +6604,7 @@ function buildProxyToolHelperDeps() {
6601
6604
  loadProjectSummaryForTool: (params) => loadProjectSummaryForTool(params, buildProjectToolDeps()),
6602
6605
  parseToolEnvelopeFromRPCResult: (resultObj) =>
6603
6606
  parseToolEnvelopeFromRPCResult(resultObj, buildGatewayTransportDeps()),
6604
- ctxpackPushToolNames: CTXPACK_PUSH_TOOL_NAMES,
6607
+ ctxpackUpdateToolNames: CTXPACK_UPDATE_TOOL_NAMES,
6605
6608
  localProjectToolNames: LOCAL_PROJECT_TOOL_NAMES,
6606
6609
  localCtxpackMergeToolNames: LOCAL_CTXPACK_MERGE_TOOL_NAMES,
6607
6610
  autoCtxpackSyncTracker,
@@ -6626,7 +6629,7 @@ function buildProxyResponsePipelineDeps() {
6626
6629
 
6627
6630
  function buildProxyGatewayRequestDeps() {
6628
6631
  return {
6629
- injectCtxpackPushDefaults,
6632
+ injectCtxpackUpdateDefaults,
6630
6633
  injectCtxpackPreflightToken,
6631
6634
  stripLocalOnlyToolArgs,
6632
6635
  postJSON,
@@ -7016,7 +7019,7 @@ async function runDoctor(flags) {
7016
7019
  rows,
7017
7020
  "fail",
7018
7021
  "stale baseline",
7019
- `local ${localBaseVersionID} != server ${serverCurrentVersionID}. Run ctxpack pull before safe push.`,
7022
+ `local ${localBaseVersionID} != server ${serverCurrentVersionID}. Run ctxpack pull before ctxpack.update.`,
7020
7023
  );
7021
7024
  } else {
7022
7025
  addDoctorCheck(rows, "ok", "stale baseline", `baseline is current (${serverCurrentVersionID})`);
@@ -7777,8 +7780,8 @@ function stripLocalOnlyToolArgs(requestObj) {
7777
7780
  return stripLocalOnlyToolArgsImpl(requestObj);
7778
7781
  }
7779
7782
 
7780
- function injectCtxpackPushDefaults(requestObj, toolName, workspaceDir) {
7781
- return injectCtxpackPushDefaultsImpl(
7783
+ function injectCtxpackUpdateDefaults(requestObj, toolName, workspaceDir) {
7784
+ return injectCtxpackUpdateDefaultsImpl(
7782
7785
  {
7783
7786
  requestObj,
7784
7787
  toolName,
@@ -8354,6 +8357,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
8354
8357
  normalizeBotRunnerConfigContents,
8355
8358
  defaultLocalBotBridgeCommand,
8356
8359
  resolveRunnerExecutionPlan,
8360
+ resolveRunnerExecutionPlanForRole,
8357
8361
  normalizeRunnerRoute,
8358
8362
  buildRunnerRouteNameSuggestion,
8359
8363
  buildRunnerRoutePayload,
@@ -8371,6 +8375,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
8371
8375
  normalizeRunnerTriggerPolicy,
8372
8376
  evaluateTelegramRunnerTrigger,
8373
8377
  processRunnerSelectedRecord,
8378
+ runRunnerAIExecution,
8374
8379
  formatBotReplyArchiveComment,
8375
8380
  findArchivedBotReplyRecord,
8376
8381
  parseArchivedChatComment,
@@ -288,6 +288,51 @@ export function normalizeExecutionArtifacts(rawArtifacts) {
288
288
  .filter(Boolean);
289
289
  }
290
290
 
291
+ function normalizeExecutionCtxpackFile(rawFile) {
292
+ const file = safeObject(rawFile);
293
+ const filePath = firstNonEmptyString([
294
+ file.path,
295
+ file.file,
296
+ file.file_path,
297
+ file.filePath,
298
+ file.location,
299
+ ]);
300
+ if (!filePath) {
301
+ return null;
302
+ }
303
+ const operation = normalizeArtifactOperation(
304
+ file.operation
305
+ || file.op
306
+ || file.action,
307
+ );
308
+ const content = firstNonEmptyString([
309
+ file.content,
310
+ file.body,
311
+ file.text,
312
+ file.markdown,
313
+ ]);
314
+ if (operation !== "delete" && !content) {
315
+ return null;
316
+ }
317
+ return {
318
+ path: filePath,
319
+ content,
320
+ docType: firstNonEmptyString([
321
+ file.doc_type,
322
+ file.docType,
323
+ file.kind,
324
+ file.type,
325
+ ]),
326
+ operation,
327
+ };
328
+ }
329
+
330
+ export function normalizeExecutionCtxpackFiles(rawFiles) {
331
+ return ensureArray(rawFiles)
332
+ .map((item) => normalizeExecutionCtxpackFile(item))
333
+ .filter(Boolean);
334
+ }
335
+
291
336
  function normalizeExecutionWorkItem(rawItem) {
292
337
  const item = safeObject(rawItem);
293
338
  const title = firstNonEmptyString([
@@ -345,6 +390,35 @@ function listRegularFilesRecursive(rootDir) {
345
390
  return output;
346
391
  }
347
392
 
393
+ function normalizeWorkspaceRelativeArtifactPath(rawPath = "") {
394
+ return String(rawPath || "").trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
395
+ }
396
+
397
+ function shouldTrackWorkspaceArtifactRelativePath(rawPath = "") {
398
+ const normalized = normalizeWorkspaceRelativeArtifactPath(rawPath);
399
+ return Boolean(normalized) && !isInternalRuntimeArtifactPath(normalized);
400
+ }
401
+
402
+ function snapshotWorkspaceArtifactMap(rootDir) {
403
+ const snapshot = new Map();
404
+ for (const filePath of listRegularFilesRecursive(rootDir)) {
405
+ const relativePath = normalizeWorkspaceRelativeArtifactPath(path.relative(rootDir, filePath));
406
+ if (!shouldTrackWorkspaceArtifactRelativePath(relativePath)) {
407
+ continue;
408
+ }
409
+ try {
410
+ const stats = fs.statSync(filePath);
411
+ snapshot.set(normalizeArtifactPathForRuntimeCompare(relativePath), {
412
+ relativePath,
413
+ resolvedPath: path.resolve(filePath),
414
+ mtimeMs: stats.mtimeMs,
415
+ sizeBytes: stats.size,
416
+ });
417
+ } catch {}
418
+ }
419
+ return snapshot;
420
+ }
421
+
348
422
  function snapshotDirectoryMtimeMap(rootDir) {
349
423
  const snapshot = new Map();
350
424
  for (const filePath of listRegularFilesRecursive(rootDir)) {
@@ -356,6 +430,90 @@ function snapshotDirectoryMtimeMap(rootDir) {
356
430
  return snapshot;
357
431
  }
358
432
 
433
+ export function captureWorkspaceArtifactSnapshot(workspaceDir) {
434
+ const normalizedWorkspaceDir = ensureWorkspaceDir(workspaceDir);
435
+ return {
436
+ workspaceDir: normalizedWorkspaceDir,
437
+ files: snapshotWorkspaceArtifactMap(normalizedWorkspaceDir),
438
+ };
439
+ }
440
+
441
+ export function observeExecutionArtifacts({
442
+ beforeSnapshot,
443
+ workspaceDir,
444
+ reportedArtifacts = [],
445
+ }) {
446
+ const afterSnapshot = captureWorkspaceArtifactSnapshot(workspaceDir);
447
+ const beforeFiles = beforeSnapshot?.files instanceof Map ? beforeSnapshot.files : new Map();
448
+ const afterFiles = afterSnapshot.files instanceof Map ? afterSnapshot.files : new Map();
449
+ const reported = normalizeExecutionArtifacts(reportedArtifacts);
450
+ const reportedByPath = new Map(
451
+ reported
452
+ .map((item) => {
453
+ const artifact = safeObject(item);
454
+ const normalizedPath = normalizeArtifactPathForRuntimeCompare(artifact.path || artifact.relativePath || "");
455
+ if (!normalizedPath) {
456
+ return null;
457
+ }
458
+ return [normalizedPath, artifact];
459
+ })
460
+ .filter(Boolean),
461
+ );
462
+ const observedArtifacts = [];
463
+ const observedPathKeys = new Set();
464
+ const allPathKeys = new Set([
465
+ ...beforeFiles.keys(),
466
+ ...afterFiles.keys(),
467
+ ]);
468
+ for (const pathKey of allPathKeys) {
469
+ const beforeEntry = beforeFiles.get(pathKey);
470
+ const afterEntry = afterFiles.get(pathKey);
471
+ let operation = "";
472
+ if (!beforeEntry && afterEntry) {
473
+ operation = "create";
474
+ } else if (beforeEntry && !afterEntry) {
475
+ operation = "delete";
476
+ } else if (
477
+ beforeEntry
478
+ && afterEntry
479
+ && (
480
+ beforeEntry.mtimeMs !== afterEntry.mtimeMs
481
+ || beforeEntry.sizeBytes !== afterEntry.sizeBytes
482
+ )
483
+ ) {
484
+ operation = "update";
485
+ }
486
+ if (!operation) {
487
+ continue;
488
+ }
489
+ const reportedArtifact = safeObject(reportedByPath.get(pathKey));
490
+ const relativePath = normalizeWorkspaceRelativeArtifactPath(
491
+ afterEntry?.relativePath
492
+ || beforeEntry?.relativePath
493
+ || reportedArtifact.path
494
+ || "",
495
+ );
496
+ if (!shouldTrackWorkspaceArtifactRelativePath(relativePath)) {
497
+ continue;
498
+ }
499
+ observedPathKeys.add(pathKey);
500
+ observedArtifacts.push({
501
+ path: relativePath,
502
+ kind: String(reportedArtifact.kind || "").trim(),
503
+ operation,
504
+ });
505
+ }
506
+ return {
507
+ snapshot: afterSnapshot,
508
+ reportedArtifacts: reported,
509
+ artifacts: observedArtifacts,
510
+ unmatchedReportedArtifacts: reported.filter((item) => {
511
+ const normalizedPath = normalizeArtifactPathForRuntimeCompare(item.path || item.relativePath || "");
512
+ return normalizedPath && !observedPathKeys.has(normalizedPath);
513
+ }),
514
+ };
515
+ }
516
+
359
517
  function resolveRunnerUserHomeDir(env) {
360
518
  const envHome = firstNonEmptyString([env?.CLAUDE_HOME, env?.USERPROFILE, env?.HOME, os.homedir()]);
361
519
  return envHome ? path.resolve(envHome) : "";
@@ -419,24 +577,20 @@ function detectExternalArtifactBoundaryViolations({ snapshot, workspaceDir }) {
419
577
  export function validateWorkspaceArtifacts(artifacts, workspaceDir, options = {}) {
420
578
  const normalizedWorkspaceDir = ensureWorkspaceDir(workspaceDir);
421
579
  const normalizedPermissionMode = normalizeLocalAIPermissionMode(options.permissionMode, "read_only");
422
- const internalRuntimeRoots = [
423
- path.join(normalizedWorkspaceDir, ".claude", "plans"),
424
- ];
425
580
  const normalizedArtifacts = normalizeExecutionArtifacts(artifacts).map((artifact) => {
426
581
  const rawPath = String(artifact.path || "").trim();
427
582
  const resolvedPath = path.isAbsolute(rawPath)
428
583
  ? path.resolve(rawPath)
429
584
  : path.resolve(normalizedWorkspaceDir, rawPath);
430
- const internalRuntimeArtifact = internalRuntimeRoots.some((rootDir) => (
431
- isPathWithinWorkspace(resolvedPath, rootDir)
432
- ));
585
+ const relativePath = isPathWithinWorkspace(resolvedPath, normalizedWorkspaceDir)
586
+ ? path.relative(normalizedWorkspaceDir, resolvedPath) || "."
587
+ : "";
588
+ const internalRuntimeArtifact = isInternalRuntimeArtifactPath(relativePath);
433
589
  return {
434
590
  ...artifact,
435
591
  path: rawPath,
436
592
  resolvedPath,
437
- relativePath: isPathWithinWorkspace(resolvedPath, normalizedWorkspaceDir)
438
- ? path.relative(normalizedWorkspaceDir, resolvedPath) || "."
439
- : "",
593
+ relativePath,
440
594
  internalRuntimeArtifact,
441
595
  };
442
596
  });
@@ -469,12 +623,34 @@ function normalizeArtifactPathForRuntimeCompare(rawPath = "") {
469
623
  return normalized.replace(/^\.\/+/, "");
470
624
  }
471
625
 
626
+ const INTERNAL_LOCAL_AI_RUNTIME_SCRATCH_RELATIVE_DIR = ".metheus/runner-runtime/local-ai-scratch";
627
+ const NON_PROJECT_WORKSPACE_ARTIFACT_PREFIXES = [
628
+ ".git/",
629
+ "node_modules/",
630
+ ];
631
+ const NON_PROJECT_WORKSPACE_ARTIFACT_EXACT_PATHS = new Set([
632
+ ".metheus_ctxpack_sync.json",
633
+ ".git",
634
+ "node_modules",
635
+ ]);
636
+
472
637
  export function isInternalRuntimeArtifactPath(rawPath = "") {
473
638
  const normalized = normalizeArtifactPathForRuntimeCompare(rawPath);
474
- return normalized === ".claude/plans"
475
- || normalized.startsWith(".claude/plans/")
639
+ if (!normalized) {
640
+ return false;
641
+ }
642
+ if (
643
+ normalized === INTERNAL_LOCAL_AI_RUNTIME_SCRATCH_RELATIVE_DIR
644
+ || normalized.startsWith(`${INTERNAL_LOCAL_AI_RUNTIME_SCRATCH_RELATIVE_DIR}/`)
476
645
  || normalized === ".metheus/ctxpack-cache"
477
- || normalized.startsWith(".metheus/ctxpack-cache/");
646
+ || normalized.startsWith(".metheus/ctxpack-cache/")
647
+ ) {
648
+ return true;
649
+ }
650
+ if (NON_PROJECT_WORKSPACE_ARTIFACT_EXACT_PATHS.has(normalized)) {
651
+ return true;
652
+ }
653
+ return NON_PROJECT_WORKSPACE_ARTIFACT_PREFIXES.some((prefix) => normalized.startsWith(prefix));
478
654
  }
479
655
 
480
656
  function runCodexRawText({ promptText, workspaceDir, model, permissionMode, reasoningEffort, env }) {
@@ -514,7 +690,7 @@ function runCodexRawText({ promptText, workspaceDir, model, permissionMode, reas
514
690
 
515
691
  function prepareClaudeRuntimeConfig({ workspaceDir }) {
516
692
  const normalizedWorkspaceDir = ensureWorkspaceDir(workspaceDir);
517
- const plansDirectory = path.join(normalizedWorkspaceDir, ".claude", "plans");
693
+ const plansDirectory = path.join(normalizedWorkspaceDir, ".metheus", "runner-runtime", "local-ai-scratch");
518
694
  fs.mkdirSync(plansDirectory, { recursive: true });
519
695
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-claude-settings-"));
520
696
  const settingsPath = path.join(tempDir, "settings.json");
@@ -682,6 +858,7 @@ function normalizeCliOutput(rawText) {
682
858
  replyToMessageID: intFromRawAllowZero(parsed.reply_to_message_id ?? parsed.replyToMessageID, 0),
683
859
  contract,
684
860
  artifacts: normalizeExecutionArtifacts(parsed.artifacts ?? parsed.files ?? parsed.outputs),
861
+ ctxpackFiles: normalizeExecutionCtxpackFiles(parsed.ctxpack_files ?? parsed.ctxpackFiles),
685
862
  workItems: normalizeExecutionWorkItems(parsed.work_items ?? parsed.workItems),
686
863
  raw: parsed,
687
864
  };
@@ -692,6 +869,7 @@ function normalizeCliOutput(rawText) {
692
869
  replyToMessageID: 0,
693
870
  contract: null,
694
871
  artifacts: [],
872
+ ctxpackFiles: [],
695
873
  workItems: [],
696
874
  raw: text,
697
875
  };
@@ -1321,7 +1499,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1321
1499
  "Do the work for this step now. Do not say you will start later, wait, come back later, or plan first.",
1322
1500
  "If this is a worker step, actually create/update the required project artifacts now and include them in artifacts.",
1323
1501
  executionStep.ctxpack_update_required === true || executionStep.ctxpackUpdateRequired === true
1324
- ? "This step must update project guidance/specification content that belongs in the workspace as ctxpack source material. Update concrete workspace files now. Do not treat .metheus/ctxpack-cache or .claude/plans as the final authoring target. Use official project files inside the workspace such as doc/, docs/, README.md, or other explicit project paths, and include every changed ctxpack source file in artifacts so the server ctxpack can be updated from them."
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. The workspace .metheus directory may contain official ctxpack source files, but do not treat .metheus/ctxpack-cache, .metheus/runner-runtime/local-ai-scratch, .metheus_ctxpack_sync.json, or .claude/ as final authoring targets. Use official project files such as .metheus/... source docs, doc/, docs/, README.md, or other explicit project paths."
1325
1503
  : "",
1326
1504
  executionStep.work_items_required === true || executionStep.workItemsRequired === true
1327
1505
  ? "This step must also break the work into executable governance work items. Return them in work_items with atomic titles and useful descriptions."
@@ -1381,12 +1559,12 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1381
1559
  "- Do not claim that a file, plan, document, or code change is complete unless the corresponding artifact path is present in artifacts.",
1382
1560
  "",
1383
1561
  isInternalExecutionStep
1384
- ? "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\"}],\"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 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. Use artifacts: [] only if this step truly changes no project files, and use work_items: [] only if this step truly creates no governance tasks."
1385
1563
  : responseContract.must_reply === true
1386
- ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"work_items\":[]} or {\"reply\":\"...\",\"artifacts\":[],\"work_items\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\"}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}."
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\"]}}."
1387
1565
  : terse
1388
- ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"work_items\":[]} or {\"skip\":true,\"reason\":\"...\"}."
1389
- : "Return JSON only: {\"reply\":\"...\",\"artifacts\":[],\"work_items\":[]} or {\"skip\":true,\"reason\":\"...\"}. Keep the reply concise and directly useful in a group chat.",
1566
+ ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"skip\":true,\"reason\":\"...\"}."
1567
+ : "Return JSON only: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"skip\":true,\"reason\":\"...\"}. Keep the reply concise and directly useful in a group chat.",
1390
1568
  );
1391
1569
  if (conversation?.mode === "public_multi_bot") {
1392
1570
  const roleGuidance = {
@@ -1526,6 +1704,7 @@ export function serializeLocalAIResult(result) {
1526
1704
  return JSON.stringify({
1527
1705
  reply: String(result?.reply || "").trim(),
1528
1706
  artifacts: normalizeExecutionArtifacts(result?.artifacts),
1707
+ ctxpack_files: normalizeExecutionCtxpackFiles(result?.ctxpackFiles),
1529
1708
  work_items: normalizeExecutionWorkItems(result?.workItems),
1530
1709
  ...(Array.isArray(result?.boundaryViolations) && result.boundaryViolations.length > 0
1531
1710
  ? { boundary_violations: result.boundaryViolations }
@@ -2078,6 +2257,7 @@ export function runLocalAIClient({
2078
2257
  return {
2079
2258
  ...sampleResult,
2080
2259
  artifacts: normalizeExecutionArtifacts(sampleResult?.artifacts),
2260
+ ctxpackFiles: normalizeExecutionCtxpackFiles(sampleResult?.ctxpackFiles),
2081
2261
  boundaryViolations: [],
2082
2262
  };
2083
2263
  }
@@ -2129,6 +2309,7 @@ export function runLocalAIClient({
2129
2309
  return {
2130
2310
  ...safeObject(result),
2131
2311
  artifacts: normalizeExecutionArtifacts(result?.artifacts),
2312
+ ctxpackFiles: normalizeExecutionCtxpackFiles(result?.ctxpackFiles),
2132
2313
  boundaryViolations: detectExternalArtifactBoundaryViolations({
2133
2314
  snapshot: boundarySnapshot,
2134
2315
  workspaceDir: resolvedWorkspaceDir,
@@ -24,7 +24,7 @@ export async function handleProxyGatewayRequest(
24
24
  },
25
25
  deps,
26
26
  ) {
27
- const injectCtxpackPushDefaults = requireDependency(deps, "injectCtxpackPushDefaults");
27
+ const injectCtxpackUpdateDefaults = requireDependency(deps, "injectCtxpackUpdateDefaults");
28
28
  const injectCtxpackPreflightToken = requireDependency(deps, "injectCtxpackPreflightToken");
29
29
  const stripLocalOnlyToolArgs = requireDependency(deps, "stripLocalOnlyToolArgs");
30
30
  const postJSON = requireDependency(deps, "postJSON");
@@ -32,7 +32,7 @@ export async function handleProxyGatewayRequest(
32
32
  const appendCtxpackConflictHintToErrorResponse = requireDependency(deps, "appendCtxpackConflictHintToErrorResponse");
33
33
  const applyProxyResponsePatches = requireDependency(deps, "applyProxyResponsePatches");
34
34
 
35
- const requestWithDefaults = injectCtxpackPushDefaults(requestObj, toolName, requestWorkspaceDir);
35
+ const requestWithDefaults = injectCtxpackUpdateDefaults(requestObj, toolName, requestWorkspaceDir);
36
36
  const outboundRequestObj = stripLocalOnlyToolArgs(
37
37
  await injectCtxpackPreflightToken(
38
38
  requestWithDefaults,
@@ -390,7 +390,7 @@ export function stripLocalOnlyToolArgs(requestObj) {
390
390
  return { ...requestObj, params: nextParams };
391
391
  }
392
392
 
393
- export function injectCtxpackPushDefaults(
393
+ export function injectCtxpackUpdateDefaults(
394
394
  {
395
395
  requestObj,
396
396
  toolName,
@@ -402,10 +402,10 @@ export function injectCtxpackPushDefaults(
402
402
  const loadWorkspaceMeta = requireDependency(deps, "loadWorkspaceMeta");
403
403
  const isUUID = requireDependency(deps, "isUUID");
404
404
 
405
- const pushToolNames = ensureArray(deps?.ctxpackPushToolNames);
405
+ const updateToolNames = ensureArray(deps?.ctxpackUpdateToolNames);
406
406
  if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
407
407
  const normalizedTool = String(toolName || "").trim().toLowerCase();
408
- if (!pushToolNames.includes(normalizedTool)) {
408
+ if (!updateToolNames.includes(normalizedTool)) {
409
409
  return requestObj;
410
410
  }
411
411
 
@@ -467,13 +467,13 @@ export async function injectCtxpackPreflightToken(
467
467
  const postJSON = requireDependency(deps, "postJSON");
468
468
  const tryJsonParse = requireDependency(deps, "tryJsonParse");
469
469
 
470
- const pushToolNames = ensureArray(deps?.ctxpackPushToolNames);
470
+ const updateToolNames = ensureArray(deps?.ctxpackUpdateToolNames);
471
471
 
472
472
  if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
473
473
  if (!token) return requestObj;
474
474
 
475
475
  const normalizedTool = String(toolName || "").trim().toLowerCase();
476
- if (!pushToolNames.includes(normalizedTool)) {
476
+ if (!updateToolNames.includes(normalizedTool)) {
477
477
  return requestObj;
478
478
  }
479
479
 
@@ -591,7 +591,7 @@ export async function appendCtxpackConflictHintToErrorResponse(
591
591
  autoPullHint = ` auto-pull failed: ${String(err?.message || err)}`;
592
592
  }
593
593
  }
594
- const hint = `stale baseline detected. run 'metheus-governance-mcp ctxpack pull --project-id ${projectIDHint} --base-url ${baseURL}', merge local edits, then retry safe push.${autoPullHint}`;
594
+ const hint = `stale baseline detected. run 'metheus-governance-mcp ctxpack pull --project-id ${projectIDHint} --base-url ${baseURL}', merge local edits, then retry ctxpack.update.${autoPullHint}`;
595
595
  const nextError = { ...errObj, message: `${message}; ${hint}` };
596
596
  return { ...out, error: nextError };
597
597
  }