metheus-governance-mcp-cli 0.2.6 → 0.2.8
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 +21 -14
- package/cli.mjs +522 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Metheus Governance MCP helper CLI.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Compatibility note: legacy command alias `metheus-governance-mcp` is still supported.
|
|
6
|
+
|
|
7
|
+
- `metheus-governance-mcp-cli` (no args): bootstrap mode
|
|
6
8
|
- checks auth token
|
|
7
9
|
- if token is missing/expired, starts `auth login`
|
|
8
10
|
- checks Codex/Claude MCP registration
|
|
@@ -23,7 +25,7 @@ npm install -g metheus-governance-mcp-cli
|
|
|
23
25
|
Run in your project folder:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
metheus-governance-mcp --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com
|
|
28
|
+
metheus-governance-mcp-cli --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
If auth is not ready, login starts automatically, then MCP registration is completed.
|
|
@@ -31,7 +33,7 @@ If auth is not ready, login starts automatically, then MCP registration is compl
|
|
|
31
33
|
## Setup
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
|
-
metheus-governance-mcp setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com
|
|
36
|
+
metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
`project-id` can be omitted if your current folder (or parent) has `.metheus_ctxpack_sync.json`.
|
|
@@ -39,7 +41,7 @@ metheus-governance-mcp setup --project-id <project_uuid> --ctxpack-key "<ctxpack
|
|
|
39
41
|
## Doctor
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
|
-
metheus-governance-mcp doctor --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com
|
|
44
|
+
metheus-governance-mcp-cli doctor --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
Checks:
|
|
@@ -70,30 +72,35 @@ These tools accept `project_id` and return:
|
|
|
70
72
|
- missing local cache -> download
|
|
71
73
|
- same version -> keep current
|
|
72
74
|
- newer server version -> update local cache
|
|
75
|
+
- workspace path -> follows active client workspace/root when provided by Codex/Claude
|
|
73
76
|
|
|
74
77
|
Manual ctxpack pull/update:
|
|
75
78
|
|
|
76
79
|
```bash
|
|
77
|
-
metheus-governance-mcp ctxpack pull --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com
|
|
80
|
+
metheus-governance-mcp-cli ctxpack pull --project-id <project_uuid> --base-url https://metheus.gesiaplatform.com
|
|
78
81
|
```
|
|
79
82
|
|
|
80
83
|
When `workitem.list` returns empty, proxy appends a hint to call `project.summary` first.
|
|
81
84
|
|
|
85
|
+
For `ctxpack` push conflicts (`ctxpack_conflict` / stale baseline), proxy behavior:
|
|
86
|
+
- standard conflict message with pull/merge/retry guidance
|
|
87
|
+
- auto ctxpack pull-to-local on conflict by default (`--auto-pull-on-conflict=true`)
|
|
88
|
+
|
|
82
89
|
## Auth flow (recommended)
|
|
83
90
|
|
|
84
91
|
1. Auto login and save token once (default flow: `device -> callback -> manual hint`):
|
|
85
92
|
|
|
86
93
|
```bash
|
|
87
|
-
metheus-governance-mcp auth login --base-url https://metheus.gesiaplatform.com
|
|
94
|
+
metheus-governance-mcp-cli auth login --base-url https://metheus.gesiaplatform.com
|
|
88
95
|
```
|
|
89
96
|
|
|
90
97
|
Optional flags:
|
|
91
98
|
|
|
92
99
|
```bash
|
|
93
|
-
metheus-governance-mcp auth login --callback-port 43819 --open-browser true
|
|
94
|
-
metheus-governance-mcp auth login --flow device
|
|
95
|
-
metheus-governance-mcp auth login --flow callback --callback-port 43819
|
|
96
|
-
metheus-governance-mcp auth login --client-id metheus-cli --realm master --keycloak-url https://oauth3.gesia.io
|
|
100
|
+
metheus-governance-mcp-cli auth login --callback-port 43819 --open-browser true
|
|
101
|
+
metheus-governance-mcp-cli auth login --flow device
|
|
102
|
+
metheus-governance-mcp-cli auth login --flow callback --callback-port 43819
|
|
103
|
+
metheus-governance-mcp-cli auth login --client-id metheus-cli --realm master --keycloak-url https://oauth3.gesia.io
|
|
97
104
|
```
|
|
98
105
|
|
|
99
106
|
Keycloak client requirement for auto login:
|
|
@@ -106,25 +113,25 @@ Keycloak client requirement for auto login:
|
|
|
106
113
|
Manual fallback:
|
|
107
114
|
|
|
108
115
|
```bash
|
|
109
|
-
metheus-governance-mcp auth login --manual true --base-url https://metheus.gesiaplatform.com
|
|
116
|
+
metheus-governance-mcp-cli auth login --manual true --base-url https://metheus.gesiaplatform.com
|
|
110
117
|
```
|
|
111
118
|
|
|
112
119
|
2. Check token status:
|
|
113
120
|
|
|
114
121
|
```bash
|
|
115
|
-
metheus-governance-mcp auth status
|
|
122
|
+
metheus-governance-mcp-cli auth status
|
|
116
123
|
```
|
|
117
124
|
|
|
118
125
|
3. If token is rotated, set directly:
|
|
119
126
|
|
|
120
127
|
```bash
|
|
121
|
-
metheus-governance-mcp auth set --token "<access_token>" --refresh-token "<refresh_token>"
|
|
128
|
+
metheus-governance-mcp-cli auth set --token "<access_token>" --refresh-token "<refresh_token>"
|
|
122
129
|
```
|
|
123
130
|
|
|
124
131
|
4. Remove local token:
|
|
125
132
|
|
|
126
133
|
```bash
|
|
127
|
-
metheus-governance-mcp auth clear
|
|
134
|
+
metheus-governance-mcp-cli auth clear
|
|
128
135
|
```
|
|
129
136
|
|
|
130
137
|
`proxy` priority:
|
package/cli.mjs
CHANGED
|
@@ -16,27 +16,31 @@ const DEFAULT_SERVER_NAME = "metheus-governance-mcp";
|
|
|
16
16
|
const AUTH_STORE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-auth.json");
|
|
17
17
|
const CTXPACK_CACHE_RELATIVE_DIR = path.join(".metheus", "ctxpack-cache");
|
|
18
18
|
const CTXPACK_META_FILENAME = ".metheus_ctxpack_sync.json";
|
|
19
|
+
const CTXPACK_PUSH_TOOL_NAMES = ["ctxpack.push", "ctxpack.update", "ctxpack.save"];
|
|
19
20
|
const CLI_META = loadCLIMeta();
|
|
20
21
|
const CLI_NAME = CLI_META.name || "metheus-governance-mcp-cli";
|
|
21
22
|
const CLI_VERSION = CLI_META.version || "0.0.0";
|
|
23
|
+
const AUTO_CTXPACK_SYNC_INTERVAL_MS = 60 * 1000;
|
|
24
|
+
const autoCtxpackSyncTracker = new Map();
|
|
22
25
|
|
|
23
26
|
function printUsage() {
|
|
27
|
+
const cmd = CLI_NAME;
|
|
24
28
|
process.stderr.write(
|
|
25
29
|
[
|
|
26
30
|
"Metheus Governance MCP CLI",
|
|
27
31
|
"",
|
|
28
32
|
"Usage:",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
` ${cmd} [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--flow <auto|device|callback|manual>]`,
|
|
34
|
+
` ${cmd} init [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--flow <auto|device|callback|manual>]`,
|
|
35
|
+
` ${cmd} setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--name <server_name>]`,
|
|
36
|
+
` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>]`,
|
|
37
|
+
` ${cmd} proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--include-drafts <true|false>] [--auto-pull-on-conflict <true|false>] [--timeout-seconds <n>]`,
|
|
38
|
+
` ${cmd} ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path>] [--paths <csv>] [--timeout-seconds <n>]`,
|
|
39
|
+
` ${cmd} auth status`,
|
|
40
|
+
` ${cmd} auth login [--base-url <url>] [--flow <auto|device|callback|manual>] [--keycloak-url <url>] [--realm <name>] [--client-id <id>] [--open-browser <true|false>] [--callback-port <n>] [--timeout-seconds <n>] [--manual <true|false>]`,
|
|
41
|
+
` ${cmd} auth set --token <jwt> [--refresh-token <token>] [--base-url <url>]`,
|
|
42
|
+
` ${cmd} auth clear`,
|
|
43
|
+
` ${cmd} -v | --version`,
|
|
40
44
|
"",
|
|
41
45
|
"Environment:",
|
|
42
46
|
" METHEUS_TOKEN or MCP_AUTH_TOKEN is used first for proxy requests.",
|
|
@@ -109,6 +113,91 @@ function resolveWorkspaceDir(rawPath) {
|
|
|
109
113
|
return path.resolve(process.cwd());
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
function fileURIToLocalPath(rawValue) {
|
|
117
|
+
const value = String(rawValue || "").trim();
|
|
118
|
+
if (!value || !/^file:\/\//i.test(value)) return "";
|
|
119
|
+
try {
|
|
120
|
+
const parsed = new URL(value);
|
|
121
|
+
if (parsed.protocol !== "file:") return "";
|
|
122
|
+
const pathname = decodeURIComponent(parsed.pathname || "");
|
|
123
|
+
if (!pathname) return "";
|
|
124
|
+
if (process.platform === "win32") {
|
|
125
|
+
// file:///C:/repo -> C:\repo
|
|
126
|
+
const winPath = pathname.replace(/^\/+([a-zA-Z]:\/)/, "$1").replace(/\//g, "\\");
|
|
127
|
+
return winPath;
|
|
128
|
+
}
|
|
129
|
+
return pathname;
|
|
130
|
+
} catch {
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function firstNonEmptyString(values) {
|
|
136
|
+
for (const value of values) {
|
|
137
|
+
const text = String(value || "").trim();
|
|
138
|
+
if (text) return text;
|
|
139
|
+
}
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function extractWorkspaceCandidateFromRequest(requestObj, toolArgs) {
|
|
144
|
+
const params = safeObject(requestObj?.params);
|
|
145
|
+
const meta = safeObject(params._meta);
|
|
146
|
+
const args = safeObject(toolArgs);
|
|
147
|
+
const rawCandidate = firstNonEmptyString([
|
|
148
|
+
args.workspace_dir,
|
|
149
|
+
args.workspaceDir,
|
|
150
|
+
params.workspace_dir,
|
|
151
|
+
params.workspaceDir,
|
|
152
|
+
params.cwd,
|
|
153
|
+
params.root_path,
|
|
154
|
+
params.rootPath,
|
|
155
|
+
params.root_uri,
|
|
156
|
+
params.rootUri,
|
|
157
|
+
meta.workspace_dir,
|
|
158
|
+
meta.workspaceDir,
|
|
159
|
+
meta.cwd,
|
|
160
|
+
meta.root_path,
|
|
161
|
+
meta.rootPath,
|
|
162
|
+
meta.root_uri,
|
|
163
|
+
meta.rootUri,
|
|
164
|
+
]);
|
|
165
|
+
const fileCandidate = fileURIToLocalPath(rawCandidate);
|
|
166
|
+
const candidate = firstNonEmptyString([fileCandidate, rawCandidate]);
|
|
167
|
+
if (!candidate) return "";
|
|
168
|
+
return resolveWorkspaceDir(candidate);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extractWorkspaceCandidateFromEnv() {
|
|
172
|
+
const rawCandidate = firstNonEmptyString([
|
|
173
|
+
process.env.METHEUS_WORKSPACE_DIR,
|
|
174
|
+
process.env.CLAUDE_WORKSPACE_DIR,
|
|
175
|
+
process.env.CLAUDE_PROJECT_DIR,
|
|
176
|
+
process.env.CODEX_WORKSPACE_DIR,
|
|
177
|
+
process.env.WORKSPACE_DIR,
|
|
178
|
+
process.env.WORKSPACE_FOLDER,
|
|
179
|
+
process.env.VSCODE_CWD,
|
|
180
|
+
process.env.PWD,
|
|
181
|
+
process.env.INIT_CWD,
|
|
182
|
+
]);
|
|
183
|
+
const fileCandidate = fileURIToLocalPath(rawCandidate);
|
|
184
|
+
const candidate = firstNonEmptyString([fileCandidate, rawCandidate]);
|
|
185
|
+
if (!candidate) return "";
|
|
186
|
+
return resolveWorkspaceDir(candidate);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function resolveWorkspaceDirForRequest(defaultWorkspaceDir, requestObj, toolArgs) {
|
|
190
|
+
const requestCandidate = extractWorkspaceCandidateFromRequest(requestObj, toolArgs);
|
|
191
|
+
const envCandidate = extractWorkspaceCandidateFromEnv();
|
|
192
|
+
const candidate = firstNonEmptyString([
|
|
193
|
+
requestCandidate,
|
|
194
|
+
envCandidate,
|
|
195
|
+
defaultWorkspaceDir,
|
|
196
|
+
process.cwd(),
|
|
197
|
+
]);
|
|
198
|
+
return resolveWorkspaceDir(candidate);
|
|
199
|
+
}
|
|
200
|
+
|
|
112
201
|
function homeCtxpackCacheRootDir() {
|
|
113
202
|
const home = String(process.env.USERPROFILE || process.env.HOME || "").trim();
|
|
114
203
|
if (!home) {
|
|
@@ -1258,6 +1347,7 @@ async function runCtxpackPull(flags) {
|
|
|
1258
1347
|
const workspaceMeta = loadWorkspaceMeta(process.cwd());
|
|
1259
1348
|
const projectID = String(flags["project-id"] || workspaceMeta.project_id || "").trim();
|
|
1260
1349
|
const workspaceDir = resolveWorkspaceDir(flags["workspace-dir"] || process.cwd());
|
|
1350
|
+
const pathsCSV = String(flags.paths || "").trim();
|
|
1261
1351
|
if (!projectID) {
|
|
1262
1352
|
process.stderr.write("Missing project_id. Use --project-id <uuid> or run inside a synced ctxpack workspace.\n");
|
|
1263
1353
|
process.exitCode = 1;
|
|
@@ -1289,6 +1379,7 @@ async function runCtxpackPull(flags) {
|
|
|
1289
1379
|
includeCtxpack: true,
|
|
1290
1380
|
syncCtxpackLocal: true,
|
|
1291
1381
|
workspaceDir,
|
|
1382
|
+
ctxpackPaths: pathsCSV,
|
|
1292
1383
|
});
|
|
1293
1384
|
process.stdout.write(`${buildProjectSummaryText(summary)}\n`);
|
|
1294
1385
|
|
|
@@ -1509,6 +1600,110 @@ async function runDoctor(flags) {
|
|
|
1509
1600
|
addDoctorCheck(rows, "fail", "ctxpack sync", summary.ctxpack_sync_message || "ctxpack sync failed");
|
|
1510
1601
|
}
|
|
1511
1602
|
|
|
1603
|
+
const localMeta = loadWorkspaceMeta(context.workspaceDir);
|
|
1604
|
+
const localBaseVersionID = firstNonEmptyString([localMeta.base_version_id, localMeta.version_id]);
|
|
1605
|
+
const serverCurrentVersionID = firstNonEmptyString([summary.current_version_id, summary.ctxpack_version_id]);
|
|
1606
|
+
if (summary.ctxpack_id) {
|
|
1607
|
+
if (!serverCurrentVersionID) {
|
|
1608
|
+
addDoctorCheck(rows, "warn", "stale baseline", "server current_version_id is missing");
|
|
1609
|
+
} else if (!localBaseVersionID) {
|
|
1610
|
+
addDoctorCheck(
|
|
1611
|
+
rows,
|
|
1612
|
+
"warn",
|
|
1613
|
+
"stale baseline",
|
|
1614
|
+
"local base_version_id is missing. Run: metheus-governance-mcp ctxpack pull --project-id <project_id>",
|
|
1615
|
+
);
|
|
1616
|
+
} else if (localBaseVersionID !== serverCurrentVersionID) {
|
|
1617
|
+
addDoctorCheck(
|
|
1618
|
+
rows,
|
|
1619
|
+
"fail",
|
|
1620
|
+
"stale baseline",
|
|
1621
|
+
`local ${localBaseVersionID} != server ${serverCurrentVersionID}. Run ctxpack pull before safe push.`,
|
|
1622
|
+
);
|
|
1623
|
+
} else {
|
|
1624
|
+
addDoctorCheck(rows, "ok", "stale baseline", `baseline is current (${serverCurrentVersionID})`);
|
|
1625
|
+
}
|
|
1626
|
+
} else {
|
|
1627
|
+
addDoctorCheck(rows, "warn", "stale baseline", "ctxpack is not available for this project");
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (summary.ctxpack_id) {
|
|
1631
|
+
try {
|
|
1632
|
+
const encodedProjectID = encodeURIComponent(context.projectID);
|
|
1633
|
+
const statsRaw = await getJSONWithAuth(
|
|
1634
|
+
`${context.baseURL}/api/v1/projects/${encodedProjectID}/ctxpack/stats`,
|
|
1635
|
+
timeoutSeconds,
|
|
1636
|
+
token,
|
|
1637
|
+
);
|
|
1638
|
+
const stats = safeObject(statsRaw);
|
|
1639
|
+
const strictConflict = Boolean(stats.strict_conflict);
|
|
1640
|
+
const preflightMode = String(stats.preflight_mode || "").trim().toLowerCase() || "off";
|
|
1641
|
+
const directPushMode = String(stats.direct_push_mode || "").trim().toLowerCase() || "unknown";
|
|
1642
|
+
const allowLegacyPush = Boolean(stats.allow_legacy_push);
|
|
1643
|
+
const forceAdminOnly = Boolean(stats.force_admin_only);
|
|
1644
|
+
const readOnlyMode = Boolean(stats.read_only_mode);
|
|
1645
|
+
const mergeMinApprovalsRaw = Number.parseInt(String(stats.merge_min_approvals ?? "1"), 10);
|
|
1646
|
+
const mergeMinApprovals = Number.isFinite(mergeMinApprovalsRaw) ? mergeMinApprovalsRaw : 1;
|
|
1647
|
+
|
|
1648
|
+
if (strictConflict) {
|
|
1649
|
+
addDoctorCheck(rows, "ok", "server policy strict_conflict", "enabled");
|
|
1650
|
+
} else {
|
|
1651
|
+
addDoctorCheck(rows, "warn", "server policy strict_conflict", "disabled (stale overwrite risk)");
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (preflightMode === "enforce") {
|
|
1655
|
+
addDoctorCheck(rows, "ok", "server policy preflight_mode", "enforce");
|
|
1656
|
+
} else if (preflightMode === "warn") {
|
|
1657
|
+
addDoctorCheck(rows, "warn", "server policy preflight_mode", "warn (blocking not enabled yet)");
|
|
1658
|
+
} else {
|
|
1659
|
+
addDoctorCheck(rows, "warn", "server policy preflight_mode", `${preflightMode || "off"} (recommended: enforce)`);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
if (directPushMode === "disabled") {
|
|
1663
|
+
addDoctorCheck(rows, "ok", "server policy direct_push_mode", "disabled (merge-request workflow)");
|
|
1664
|
+
} else if (directPushMode === "admin_only") {
|
|
1665
|
+
addDoctorCheck(rows, "ok", "server policy direct_push_mode", "admin_only (merge-first default)");
|
|
1666
|
+
} else if (directPushMode === "enabled") {
|
|
1667
|
+
addDoctorCheck(rows, "warn", "server policy direct_push_mode", "enabled (writer direct push is open)");
|
|
1668
|
+
} else {
|
|
1669
|
+
addDoctorCheck(rows, "warn", "server policy direct_push_mode", directPushMode || "unknown");
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
if (mergeMinApprovals >= 2) {
|
|
1673
|
+
addDoctorCheck(rows, "ok", "server policy merge_min_approvals", String(mergeMinApprovals));
|
|
1674
|
+
} else if (mergeMinApprovals === 1) {
|
|
1675
|
+
addDoctorCheck(rows, "warn", "server policy merge_min_approvals", "1 (recommended: >=2 for team review)");
|
|
1676
|
+
} else {
|
|
1677
|
+
addDoctorCheck(rows, "fail", "server policy merge_min_approvals", String(mergeMinApprovals));
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
if (allowLegacyPush) {
|
|
1681
|
+
addDoctorCheck(rows, "warn", "server policy allow_legacy_push", "enabled (recommended: false)");
|
|
1682
|
+
} else {
|
|
1683
|
+
addDoctorCheck(rows, "ok", "server policy allow_legacy_push", "disabled");
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
if (forceAdminOnly) {
|
|
1687
|
+
addDoctorCheck(rows, "ok", "server policy force_admin_only", "enabled");
|
|
1688
|
+
} else {
|
|
1689
|
+
addDoctorCheck(rows, "warn", "server policy force_admin_only", "disabled (writers may force/rollback)");
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
if (readOnlyMode) {
|
|
1693
|
+
addDoctorCheck(rows, "warn", "server mode", "ctxpack read-only mode is enabled");
|
|
1694
|
+
} else {
|
|
1695
|
+
addDoctorCheck(rows, "ok", "server mode", "ctxpack writable");
|
|
1696
|
+
}
|
|
1697
|
+
} catch (err) {
|
|
1698
|
+
addDoctorCheck(
|
|
1699
|
+
rows,
|
|
1700
|
+
"warn",
|
|
1701
|
+
"server ctxpack policy",
|
|
1702
|
+
`unable to read /ctxpack/stats (${String(err?.message || err)})`,
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1512
1707
|
const smokeCalls = [
|
|
1513
1708
|
{
|
|
1514
1709
|
tool: "workitem.list",
|
|
@@ -1854,6 +2049,7 @@ function hasAllCtxpackFiles(baseDir, files) {
|
|
|
1854
2049
|
function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack, workspaceDir }) {
|
|
1855
2050
|
const ctxpackID = String(ctxpack?.ctxpack_id || "").trim();
|
|
1856
2051
|
const version = String(ctxpack?.version || "").trim();
|
|
2052
|
+
const versionID = String(ctxpack?.version_id || ctxpack?.current_version_id || "").trim();
|
|
1857
2053
|
const status = String(ctxpack?.status || "").trim() || "draft";
|
|
1858
2054
|
const files = normalizeCtxpackFiles(ctxpack?.files);
|
|
1859
2055
|
const resolvedWorkspaceDir = resolveWorkspaceDir(workspaceDir);
|
|
@@ -1871,14 +2067,15 @@ function syncCtxpackToLocalCache({ siteBaseURL, projectID, ctxpack, workspaceDir
|
|
|
1871
2067
|
};
|
|
1872
2068
|
}
|
|
1873
2069
|
|
|
1874
|
-
const remoteSig = `${ctxpackID}|${version}|${status}|${files.length}`;
|
|
2070
|
+
const remoteSig = `${ctxpackID}|${versionID}|${version}|${status}|${files.length}`;
|
|
1875
2071
|
const previousMeta = loadCtxpackMeta(metaPath);
|
|
1876
2072
|
const previousSig = previousMeta
|
|
1877
|
-
? `${String(previousMeta.ctxpack_id || "").trim()}|${String(previousMeta.version || "").trim()}|${String(previousMeta.status || "").trim()}|${Number.parseInt(String(previousMeta.files_count || 0), 10) || 0}`
|
|
2073
|
+
? `${String(previousMeta.ctxpack_id || "").trim()}|${String(previousMeta.version_id || "").trim()}|${String(previousMeta.version || "").trim()}|${String(previousMeta.status || "").trim()}|${Number.parseInt(String(previousMeta.files_count || 0), 10) || 0}`
|
|
1878
2074
|
: "";
|
|
1879
2075
|
const payload = {
|
|
1880
2076
|
project_id: projectID,
|
|
1881
2077
|
ctxpack_id: ctxpackID,
|
|
2078
|
+
version_id: versionID,
|
|
1882
2079
|
version,
|
|
1883
2080
|
status,
|
|
1884
2081
|
files_count: files.length,
|
|
@@ -1943,6 +2140,7 @@ async function loadProjectSummaryForTool({
|
|
|
1943
2140
|
includeCtxpack,
|
|
1944
2141
|
syncCtxpackLocal,
|
|
1945
2142
|
workspaceDir,
|
|
2143
|
+
ctxpackPaths,
|
|
1946
2144
|
}) {
|
|
1947
2145
|
const encodedProjectID = encodeURIComponent(projectID);
|
|
1948
2146
|
let projectRaw = null;
|
|
@@ -1980,6 +2178,7 @@ async function loadProjectSummaryForTool({
|
|
|
1980
2178
|
let agendaSource = "";
|
|
1981
2179
|
let ctxpackID = "";
|
|
1982
2180
|
let ctxpackVersion = "";
|
|
2181
|
+
let ctxpackVersionID = "";
|
|
1983
2182
|
let ctxpackFileCount = 0;
|
|
1984
2183
|
let ctxpackSyncStatus = includeCtxpack && syncCtxpackLocal ? "not_attempted" : "disabled";
|
|
1985
2184
|
let ctxpackSyncMessage = "";
|
|
@@ -1987,7 +2186,12 @@ async function loadProjectSummaryForTool({
|
|
|
1987
2186
|
let ctxpackLocalFileCount = 0;
|
|
1988
2187
|
if (includeCtxpack) {
|
|
1989
2188
|
try {
|
|
1990
|
-
const
|
|
2189
|
+
const pathQuery = String(ctxpackPaths || "").trim();
|
|
2190
|
+
const ctxpackURL =
|
|
2191
|
+
pathQuery.length > 0
|
|
2192
|
+
? `${siteBaseURL}/api/v1/projects/${encodedProjectID}/ctxpack?paths=${encodeURIComponent(pathQuery)}`
|
|
2193
|
+
: `${siteBaseURL}/api/v1/projects/${encodedProjectID}/ctxpack`;
|
|
2194
|
+
const ctxpackRaw = await getJSONWithAuth(ctxpackURL, timeoutSeconds, token);
|
|
1991
2195
|
const ctxpack = safeObject(ctxpackRaw);
|
|
1992
2196
|
const extracted = extractAgendaFromFiles(ctxpack.files);
|
|
1993
2197
|
agenda = extracted.agenda;
|
|
@@ -1995,6 +2199,7 @@ async function loadProjectSummaryForTool({
|
|
|
1995
2199
|
ctxpackFileCount = extracted.ctxpackFileCount;
|
|
1996
2200
|
ctxpackID = String(ctxpack.ctxpack_id || "").trim();
|
|
1997
2201
|
ctxpackVersion = String(ctxpack.version || "").trim();
|
|
2202
|
+
ctxpackVersionID = String(ctxpack.version_id || "").trim();
|
|
1998
2203
|
if (syncCtxpackLocal) {
|
|
1999
2204
|
const syncResult = syncCtxpackToLocalCache({
|
|
2000
2205
|
siteBaseURL,
|
|
@@ -2035,7 +2240,10 @@ async function loadProjectSummaryForTool({
|
|
|
2035
2240
|
member_count: Number(project.member_count || 0),
|
|
2036
2241
|
created_at: String(project.created_at || "").trim(),
|
|
2037
2242
|
ctxpack_id: ctxpackID,
|
|
2243
|
+
ctxpack_version_id: ctxpackVersionID,
|
|
2038
2244
|
ctxpack_version: ctxpackVersion,
|
|
2245
|
+
base_version_id: ctxpackVersionID,
|
|
2246
|
+
current_version_id: ctxpackVersionID,
|
|
2039
2247
|
ctxpack_file_count: ctxpackFileCount,
|
|
2040
2248
|
ctxpack_sync_status: ctxpackSyncStatus,
|
|
2041
2249
|
ctxpack_sync_message: ctxpackSyncMessage,
|
|
@@ -2130,6 +2338,75 @@ function appendProjectHintToInitialize(responseObj, args) {
|
|
|
2130
2338
|
return responseObj;
|
|
2131
2339
|
}
|
|
2132
2340
|
|
|
2341
|
+
async function maybeAutoSyncCtxpackForCall({
|
|
2342
|
+
requestObj,
|
|
2343
|
+
toolName,
|
|
2344
|
+
toolArgs,
|
|
2345
|
+
args,
|
|
2346
|
+
token,
|
|
2347
|
+
workspaceDir,
|
|
2348
|
+
}) {
|
|
2349
|
+
if (!isJsonRpcMethod(requestObj, "tools/call")) return null;
|
|
2350
|
+
if (!token) return null;
|
|
2351
|
+
if (LOCAL_PROJECT_TOOL_NAMES.includes(toolName)) return null;
|
|
2352
|
+
if (String(toolName || "").trim().toLowerCase() === "ctxpack.ensure") return null;
|
|
2353
|
+
|
|
2354
|
+
const projectID = firstNonEmptyString([
|
|
2355
|
+
toolArgs?.project_id,
|
|
2356
|
+
toolArgs?.projectID,
|
|
2357
|
+
args.projectID,
|
|
2358
|
+
]);
|
|
2359
|
+
if (!isUUID(projectID)) return null;
|
|
2360
|
+
|
|
2361
|
+
const workspacePath = resolveWorkspaceDir(workspaceDir || process.cwd());
|
|
2362
|
+
const trackerKey = `${workspacePath}::${projectID}`;
|
|
2363
|
+
const now = Date.now();
|
|
2364
|
+
const lastAt = Number(autoCtxpackSyncTracker.get(trackerKey) || 0);
|
|
2365
|
+
if (lastAt > 0 && now-lastAt < AUTO_CTXPACK_SYNC_INTERVAL_MS) {
|
|
2366
|
+
return null;
|
|
2367
|
+
}
|
|
2368
|
+
autoCtxpackSyncTracker.set(trackerKey, now);
|
|
2369
|
+
|
|
2370
|
+
try {
|
|
2371
|
+
return await loadProjectSummaryForTool({
|
|
2372
|
+
siteBaseURL: normalizeSiteBaseURL(args.baseURL),
|
|
2373
|
+
projectID,
|
|
2374
|
+
token,
|
|
2375
|
+
timeoutSeconds: args.timeoutSeconds,
|
|
2376
|
+
includeCtxpack: true,
|
|
2377
|
+
syncCtxpackLocal: true,
|
|
2378
|
+
workspaceDir: workspacePath,
|
|
2379
|
+
});
|
|
2380
|
+
} catch {
|
|
2381
|
+
return null;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function appendAutoCtxpackSyncHint(responseObj, summary) {
|
|
2386
|
+
const status = String(summary?.ctxpack_sync_status || "").trim();
|
|
2387
|
+
if (!status || status === "disabled" || status === "not_attempted" || status === "current") {
|
|
2388
|
+
return responseObj;
|
|
2389
|
+
}
|
|
2390
|
+
const result = safeObject(responseObj.result);
|
|
2391
|
+
const content = ensureArray(result.content);
|
|
2392
|
+
if (!content.length) return responseObj;
|
|
2393
|
+
const first = safeObject(content[0]);
|
|
2394
|
+
if (String(first.type || "").trim() !== "text") return responseObj;
|
|
2395
|
+
const text = String(first.text || "");
|
|
2396
|
+
if (!text.trim() || text.includes("ctxpack_auto_sync:")) return responseObj;
|
|
2397
|
+
|
|
2398
|
+
let suffix = `ctxpack_auto_sync: ${status}`;
|
|
2399
|
+
const localPath = String(summary?.ctxpack_local_path || "").trim();
|
|
2400
|
+
if (localPath) suffix += ` (${localPath})`;
|
|
2401
|
+
const message = String(summary?.ctxpack_sync_message || "").trim();
|
|
2402
|
+
if (message) suffix += ` - ${message}`;
|
|
2403
|
+
|
|
2404
|
+
content[0] = { ...first, text: `${text}\n\n${suffix}` };
|
|
2405
|
+
result.content = content;
|
|
2406
|
+
responseObj.result = result;
|
|
2407
|
+
return responseObj;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2133
2410
|
async function appendWorkitemListHints(responseObj, args, toolArgs, token) {
|
|
2134
2411
|
const result = safeObject(responseObj.result);
|
|
2135
2412
|
const content = ensureArray(result.content);
|
|
@@ -2221,7 +2498,7 @@ async function appendWorkitemListHints(responseObj, args, toolArgs, token) {
|
|
|
2221
2498
|
return responseObj;
|
|
2222
2499
|
}
|
|
2223
2500
|
|
|
2224
|
-
function appendCtxpackEnsureSyncHints(responseObj, args, toolArgs) {
|
|
2501
|
+
function appendCtxpackEnsureSyncHints(responseObj, args, toolArgs, requestObj) {
|
|
2225
2502
|
const result = safeObject(responseObj.result);
|
|
2226
2503
|
const content = ensureArray(result.content);
|
|
2227
2504
|
if (!content.length) return responseObj;
|
|
@@ -2243,13 +2520,16 @@ function appendCtxpackEnsureSyncHints(responseObj, args, toolArgs) {
|
|
|
2243
2520
|
toolArgs?.project_id || toolArgs?.projectID || envelope.project_id || args.projectID || "",
|
|
2244
2521
|
).trim();
|
|
2245
2522
|
if (!projectID || !isUUID(projectID)) return responseObj;
|
|
2523
|
+
const workspaceDir = resolveWorkspaceDirForRequest(args.workspaceDir || process.cwd(), requestObj, toolArgs);
|
|
2246
2524
|
|
|
2247
2525
|
const syncResult = syncCtxpackToLocalCache({
|
|
2248
2526
|
siteBaseURL: normalizeSiteBaseURL(args.baseURL),
|
|
2249
2527
|
projectID,
|
|
2250
2528
|
ctxpack: body,
|
|
2251
|
-
workspaceDir
|
|
2529
|
+
workspaceDir,
|
|
2252
2530
|
});
|
|
2531
|
+
const baseVersionID = firstNonEmptyString([body.base_version_id, body.version_id, body.current_version_id]);
|
|
2532
|
+
const currentVersionID = firstNonEmptyString([body.current_version_id, body.version_id, body.base_version_id]);
|
|
2253
2533
|
|
|
2254
2534
|
const lines = [
|
|
2255
2535
|
"",
|
|
@@ -2264,16 +2544,189 @@ function appendCtxpackEnsureSyncHints(responseObj, args, toolArgs) {
|
|
|
2264
2544
|
if (syncResult.workspace_path) {
|
|
2265
2545
|
lines.push(`ctxpack_workspace_path: ${String(syncResult.workspace_path)}`);
|
|
2266
2546
|
}
|
|
2547
|
+
if (baseVersionID) {
|
|
2548
|
+
lines.push(`ctxpack_base_version_id: ${baseVersionID}`);
|
|
2549
|
+
}
|
|
2550
|
+
if (currentVersionID) {
|
|
2551
|
+
lines.push(`ctxpack_current_version_id: ${currentVersionID}`);
|
|
2552
|
+
}
|
|
2267
2553
|
|
|
2268
2554
|
content[0] = {
|
|
2269
2555
|
...first,
|
|
2270
2556
|
text: `${text}\n${lines.join("\n")}`,
|
|
2271
2557
|
};
|
|
2272
2558
|
result.content = content;
|
|
2559
|
+
const structured = safeObject(result.structuredContent);
|
|
2560
|
+
result.structuredContent = {
|
|
2561
|
+
...structured,
|
|
2562
|
+
sync_status: String(syncResult.sync_status || "error"),
|
|
2563
|
+
base_version_id: baseVersionID,
|
|
2564
|
+
current_version_id: currentVersionID,
|
|
2565
|
+
local_workspace_path: String(syncResult.workspace_path || workspaceDir),
|
|
2566
|
+
local_ctxpack_path: String(syncResult.local_path || ""),
|
|
2567
|
+
};
|
|
2273
2568
|
responseObj.result = result;
|
|
2274
2569
|
return responseObj;
|
|
2275
2570
|
}
|
|
2276
2571
|
|
|
2572
|
+
function injectCtxpackPushDefaults(requestObj, toolName, workspaceDir) {
|
|
2573
|
+
if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
|
|
2574
|
+
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
2575
|
+
if (!CTXPACK_PUSH_TOOL_NAMES.includes(normalizedTool)) {
|
|
2576
|
+
return requestObj;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
const params = safeObject(requestObj.params);
|
|
2580
|
+
const rawArgs = safeObject(params.arguments ?? params.args);
|
|
2581
|
+
if (String(rawArgs.base_version_id || "").trim() && String(rawArgs.push_mode || "").trim()) {
|
|
2582
|
+
return requestObj;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
const workspaceMeta = loadWorkspaceMeta(workspaceDir);
|
|
2586
|
+
const baseVersionID = firstNonEmptyString([
|
|
2587
|
+
rawArgs.base_version_id,
|
|
2588
|
+
workspaceMeta.base_version_id,
|
|
2589
|
+
workspaceMeta.version_id,
|
|
2590
|
+
]);
|
|
2591
|
+
const nextArgs = { ...rawArgs };
|
|
2592
|
+
if (!String(nextArgs.base_version_id || "").trim() && isUUID(baseVersionID)) {
|
|
2593
|
+
nextArgs.base_version_id = baseVersionID;
|
|
2594
|
+
}
|
|
2595
|
+
if (!String(nextArgs.push_mode || "").trim()) {
|
|
2596
|
+
nextArgs.push_mode = "safe";
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
const nextParams = { ...params };
|
|
2600
|
+
nextParams.arguments = nextArgs;
|
|
2601
|
+
const patched = { ...requestObj, params: nextParams };
|
|
2602
|
+
return patched;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function collectChangedPathsForPreflight(rawArgs) {
|
|
2606
|
+
const args = safeObject(rawArgs);
|
|
2607
|
+
const explicit = ensureArray(args.changed_paths ?? args.changedPaths);
|
|
2608
|
+
const raw = explicit.length > 0 ? explicit : ensureArray(args.files).map((file) => safeObject(file).path);
|
|
2609
|
+
const dedup = new Set();
|
|
2610
|
+
const out = [];
|
|
2611
|
+
for (const value of raw) {
|
|
2612
|
+
const text = String(value || "").trim();
|
|
2613
|
+
if (!text || dedup.has(text)) continue;
|
|
2614
|
+
dedup.add(text);
|
|
2615
|
+
out.push(text);
|
|
2616
|
+
}
|
|
2617
|
+
return out;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
async function injectCtxpackPreflightToken(requestObj, toolName, toolArgs, args, token, workspaceDir) {
|
|
2621
|
+
if (!isJsonRpcMethod(requestObj, "tools/call")) return requestObj;
|
|
2622
|
+
if (!token) return requestObj;
|
|
2623
|
+
|
|
2624
|
+
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
2625
|
+
if (!CTXPACK_PUSH_TOOL_NAMES.includes(normalizedTool)) {
|
|
2626
|
+
return requestObj;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
const params = safeObject(requestObj.params);
|
|
2630
|
+
const rawArgs = safeObject(params.arguments ?? params.args);
|
|
2631
|
+
if (String(rawArgs.preflight_token || rawArgs.preflightToken || "").trim()) {
|
|
2632
|
+
return requestObj;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
const projectID = firstNonEmptyString([
|
|
2636
|
+
rawArgs.project_id,
|
|
2637
|
+
rawArgs.projectID,
|
|
2638
|
+
toolArgs?.project_id,
|
|
2639
|
+
toolArgs?.projectID,
|
|
2640
|
+
args.projectID,
|
|
2641
|
+
]);
|
|
2642
|
+
if (!isUUID(projectID)) {
|
|
2643
|
+
return requestObj;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
const workspaceMeta = loadWorkspaceMeta(workspaceDir);
|
|
2647
|
+
const baseVersionID = firstNonEmptyString([
|
|
2648
|
+
rawArgs.base_version_id,
|
|
2649
|
+
rawArgs.baseVersionID,
|
|
2650
|
+
workspaceMeta.base_version_id,
|
|
2651
|
+
workspaceMeta.version_id,
|
|
2652
|
+
]);
|
|
2653
|
+
if (!isUUID(baseVersionID)) {
|
|
2654
|
+
return requestObj;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
const changedPaths = collectChangedPathsForPreflight(rawArgs);
|
|
2658
|
+
const payload = {
|
|
2659
|
+
base_version_id: baseVersionID,
|
|
2660
|
+
};
|
|
2661
|
+
if (changedPaths.length > 0) {
|
|
2662
|
+
payload.changed_paths = changedPaths;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
try {
|
|
2666
|
+
const baseURL = normalizeSiteBaseURL(args.baseURL);
|
|
2667
|
+
const preflightURL = `${baseURL}/api/v1/projects/${encodeURIComponent(projectID)}/ctxpack/preflight`;
|
|
2668
|
+
const responseText = await postJSON(preflightURL, args.timeoutSeconds, token, payload);
|
|
2669
|
+
const responseBody = safeObject(tryJsonParse(responseText));
|
|
2670
|
+
const preflightID = firstNonEmptyString([responseBody.preflight_id, responseBody.preflightID, responseBody.id]);
|
|
2671
|
+
if (!preflightID) return requestObj;
|
|
2672
|
+
|
|
2673
|
+
const nextArgs = { ...rawArgs, preflight_token: preflightID };
|
|
2674
|
+
if (!ensureArray(rawArgs.changed_paths ?? rawArgs.changedPaths).length && changedPaths.length > 0) {
|
|
2675
|
+
nextArgs.changed_paths = changedPaths;
|
|
2676
|
+
}
|
|
2677
|
+
const nextParams = { ...params, arguments: nextArgs };
|
|
2678
|
+
return { ...requestObj, params: nextParams };
|
|
2679
|
+
} catch {
|
|
2680
|
+
// Best-effort: if preflight auto-issue fails, server-side validation still enforces policy.
|
|
2681
|
+
return requestObj;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
async function appendCtxpackConflictHintToErrorResponse(responseObj, args, toolName, toolArgs, token, workspaceDir) {
|
|
2686
|
+
const out = safeObject(responseObj);
|
|
2687
|
+
const errObj = safeObject(out.error);
|
|
2688
|
+
const message = String(errObj.message || "").trim();
|
|
2689
|
+
if (!message) return responseObj;
|
|
2690
|
+
const normalizedTool = String(toolName || "").trim().toLowerCase();
|
|
2691
|
+
const normalizedMessage = message.toLowerCase();
|
|
2692
|
+
const isCtxpackConflict = normalizedMessage.includes("ctxpack_conflict") || normalizedMessage.includes("ctxpack conflict");
|
|
2693
|
+
if (!isCtxpackConflict) return responseObj;
|
|
2694
|
+
if (!normalizedTool.startsWith("ctxpack.")) return responseObj;
|
|
2695
|
+
|
|
2696
|
+
const projectIDHint = firstNonEmptyString([
|
|
2697
|
+
toolArgs?.project_id,
|
|
2698
|
+
toolArgs?.projectID,
|
|
2699
|
+
args.projectID,
|
|
2700
|
+
"<project_id>",
|
|
2701
|
+
]);
|
|
2702
|
+
const baseURL = normalizeSiteBaseURL(args.baseURL);
|
|
2703
|
+
let autoPullHint = "";
|
|
2704
|
+
if (args.autoPullOnConflict && token && isUUID(projectIDHint)) {
|
|
2705
|
+
try {
|
|
2706
|
+
const encodedProjectID = encodeURIComponent(projectIDHint);
|
|
2707
|
+
const ctxpackRaw = await getJSONWithAuth(
|
|
2708
|
+
`${baseURL}/api/v1/projects/${encodedProjectID}/ctxpack`,
|
|
2709
|
+
args.timeoutSeconds,
|
|
2710
|
+
token,
|
|
2711
|
+
);
|
|
2712
|
+
const syncResult = syncCtxpackToLocalCache({
|
|
2713
|
+
siteBaseURL: baseURL,
|
|
2714
|
+
projectID: projectIDHint,
|
|
2715
|
+
ctxpack: safeObject(ctxpackRaw),
|
|
2716
|
+
workspaceDir,
|
|
2717
|
+
});
|
|
2718
|
+
autoPullHint = ` auto-pull: ${String(syncResult.sync_status || "error")}${
|
|
2719
|
+
syncResult.local_path ? ` (${String(syncResult.local_path)})` : ""
|
|
2720
|
+
}`;
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
autoPullHint = ` auto-pull failed: ${String(err?.message || err)}`;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
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}`;
|
|
2726
|
+
const nextError = { ...errObj, message: `${message}; ${hint}` };
|
|
2727
|
+
return { ...out, error: nextError };
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2277
2730
|
async function runProxy(flags) {
|
|
2278
2731
|
const workspaceMeta = loadWorkspaceMeta(process.cwd());
|
|
2279
2732
|
const args = {
|
|
@@ -2282,11 +2735,13 @@ async function runProxy(flags) {
|
|
|
2282
2735
|
ctxpackKey: String(flags["ctxpack-key"] || buildCtxpackKeyFromMeta(workspaceMeta) || "").trim(),
|
|
2283
2736
|
workspaceDir: resolveWorkspaceDir(flags["workspace-dir"] || process.cwd()),
|
|
2284
2737
|
includeDrafts: boolFromRaw(flags["include-drafts"], true),
|
|
2738
|
+
autoPullOnConflict: boolFromRaw(flags["auto-pull-on-conflict"], true),
|
|
2285
2739
|
timeoutSeconds: intFromRaw(flags["timeout-seconds"], 30),
|
|
2286
2740
|
};
|
|
2287
2741
|
const gatewayURL = buildGatewayURL(args);
|
|
2288
2742
|
let lastRefreshAttemptAtMs = 0;
|
|
2289
2743
|
let lastRefreshError = "";
|
|
2744
|
+
let sessionWorkspaceDir = "";
|
|
2290
2745
|
|
|
2291
2746
|
const rl = readline.createInterface({
|
|
2292
2747
|
input: process.stdin,
|
|
@@ -2344,6 +2799,29 @@ async function runProxy(flags) {
|
|
|
2344
2799
|
}
|
|
2345
2800
|
|
|
2346
2801
|
const { name: toolName, args: toolArgs } = extractToolCall(requestObj);
|
|
2802
|
+
const requestWorkspaceCandidate = extractWorkspaceCandidateFromRequest(requestObj, toolArgs);
|
|
2803
|
+
if (requestWorkspaceCandidate) {
|
|
2804
|
+
sessionWorkspaceDir = requestWorkspaceCandidate;
|
|
2805
|
+
}
|
|
2806
|
+
const requestWorkspaceDir = resolveWorkspaceDir(
|
|
2807
|
+
firstNonEmptyString([
|
|
2808
|
+
requestWorkspaceCandidate,
|
|
2809
|
+
sessionWorkspaceDir,
|
|
2810
|
+
args.workspaceDir,
|
|
2811
|
+
process.cwd(),
|
|
2812
|
+
]),
|
|
2813
|
+
);
|
|
2814
|
+
let autoSyncSummary = null;
|
|
2815
|
+
if (isJsonRpcMethod(requestObj, "tools/call")) {
|
|
2816
|
+
autoSyncSummary = await maybeAutoSyncCtxpackForCall({
|
|
2817
|
+
requestObj,
|
|
2818
|
+
toolName,
|
|
2819
|
+
toolArgs,
|
|
2820
|
+
args,
|
|
2821
|
+
token,
|
|
2822
|
+
workspaceDir: requestWorkspaceDir,
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2347
2825
|
if (isJsonRpcMethod(requestObj, "tools/call") && LOCAL_PROJECT_TOOL_NAMES.includes(toolName)) {
|
|
2348
2826
|
const projectID = String(toolArgs.project_id || toolArgs.projectID || args.projectID || "").trim();
|
|
2349
2827
|
if (!projectID) {
|
|
@@ -2378,7 +2856,7 @@ async function runProxy(flags) {
|
|
|
2378
2856
|
timeoutSeconds: args.timeoutSeconds,
|
|
2379
2857
|
includeCtxpack,
|
|
2380
2858
|
syncCtxpackLocal,
|
|
2381
|
-
workspaceDir:
|
|
2859
|
+
workspaceDir: requestWorkspaceDir,
|
|
2382
2860
|
});
|
|
2383
2861
|
const text = buildProjectSummaryText(summary);
|
|
2384
2862
|
process.stdout.write(
|
|
@@ -2403,11 +2881,32 @@ async function runProxy(flags) {
|
|
|
2403
2881
|
}
|
|
2404
2882
|
|
|
2405
2883
|
try {
|
|
2406
|
-
const
|
|
2884
|
+
const requestWithDefaults = injectCtxpackPushDefaults(requestObj, toolName, requestWorkspaceDir);
|
|
2885
|
+
const outboundRequestObj = await injectCtxpackPreflightToken(
|
|
2886
|
+
requestWithDefaults,
|
|
2887
|
+
toolName,
|
|
2888
|
+
toolArgs,
|
|
2889
|
+
args,
|
|
2890
|
+
token,
|
|
2891
|
+
requestWorkspaceDir,
|
|
2892
|
+
);
|
|
2893
|
+
const responseText = await postJSON(gatewayURL, args.timeoutSeconds, token, outboundRequestObj);
|
|
2407
2894
|
if (responseText) {
|
|
2408
2895
|
const parsed = parseGatewayResponseText(responseText);
|
|
2409
|
-
if (parsed
|
|
2896
|
+
if (parsed) {
|
|
2410
2897
|
let patched = parsed;
|
|
2898
|
+
if (parsed.error) {
|
|
2899
|
+
patched = await appendCtxpackConflictHintToErrorResponse(
|
|
2900
|
+
patched,
|
|
2901
|
+
args,
|
|
2902
|
+
toolName,
|
|
2903
|
+
toolArgs,
|
|
2904
|
+
token,
|
|
2905
|
+
requestWorkspaceDir,
|
|
2906
|
+
);
|
|
2907
|
+
process.stdout.write(`${JSON.stringify(patched)}\n`);
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2411
2910
|
if (isJsonRpcMethod(requestObj, "tools/list")) {
|
|
2412
2911
|
patched = appendLocalToolToToolsList(patched);
|
|
2413
2912
|
} else if (isJsonRpcMethod(requestObj, "initialize")) {
|
|
@@ -2415,7 +2914,10 @@ async function runProxy(flags) {
|
|
|
2415
2914
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "workitem.list") {
|
|
2416
2915
|
patched = await appendWorkitemListHints(patched, args, toolArgs, token);
|
|
2417
2916
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "ctxpack.ensure") {
|
|
2418
|
-
patched = appendCtxpackEnsureSyncHints(patched, args, toolArgs);
|
|
2917
|
+
patched = appendCtxpackEnsureSyncHints(patched, args, toolArgs, requestObj);
|
|
2918
|
+
}
|
|
2919
|
+
if (autoSyncSummary) {
|
|
2920
|
+
patched = appendAutoCtxpackSyncHint(patched, autoSyncSummary);
|
|
2419
2921
|
}
|
|
2420
2922
|
process.stdout.write(`${JSON.stringify(patched)}\n`);
|
|
2421
2923
|
return;
|