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 +20 -6
- package/cli.mjs +25 -20
- package/lib/local-ai-adapters.mjs +199 -18
- package/lib/proxy-gateway-request.mjs +2 -2
- package/lib/proxy-tool-helpers.mjs +6 -6
- package/lib/runner-execution.mjs +65 -25
- package/lib/runner-orchestration.mjs +219 -8
- package/lib/selftest-runner-scenarios.mjs +738 -90
- package/lib/selftest-telegram-e2e.mjs +12 -6
- package/package.json +1 -1
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
|
-
-
|
|
501
|
-
-
|
|
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
|
-
-
|
|
568
|
-
-
|
|
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`
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
7781
|
-
return
|
|
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
|
|
431
|
-
|
|
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
|
|
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
|
-
|
|
475
|
-
|
|
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, ".
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
}
|