cleargate 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cleargate_version": "0.7.0",
3
- "generated_at": "2026-04-27T08:35:05.824Z",
2
+ "cleargate_version": "0.8.1",
3
+ "generated_at": "2026-04-27T09:03:42.064Z",
4
4
  "files": [
5
5
  {
6
6
  "path": ".claude/agents/architect.md",
package/dist/cli.cjs CHANGED
@@ -627,7 +627,7 @@ var import_commander = require("commander");
627
627
  // package.json
628
628
  var package_default = {
629
629
  name: "cleargate",
630
- version: "0.7.0",
630
+ version: "0.8.1",
631
631
  private: false,
632
632
  type: "module",
633
633
  description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
@@ -2052,9 +2052,16 @@ function mergeMcpJson(existing, entry) {
2052
2052
  next.mcpServers = servers;
2053
2053
  return JSON.stringify(next, null, 2) + "\n";
2054
2054
  }
2055
- function injectMcpJson(cwd, url) {
2055
+ function buildStdioEntry(pinVersion) {
2056
+ return {
2057
+ command: "npx",
2058
+ args: ["-y", `cleargate@${pinVersion}`, "mcp", "serve"]
2059
+ };
2060
+ }
2061
+ var STDIO_ENTRY_DEFAULT = buildStdioEntry("latest");
2062
+ function injectMcpJson(cwd, pinVersion = "latest") {
2056
2063
  const dst = path8.join(cwd, ".mcp.json");
2057
- const entry = { type: "http", url };
2064
+ const entry = buildStdioEntry(pinVersion);
2058
2065
  let existing = null;
2059
2066
  let existingRaw = null;
2060
2067
  if (fs8.existsSync(dst)) {
@@ -3162,7 +3169,7 @@ async function initHandler(opts = {}) {
3162
3169
  `);
3163
3170
  }
3164
3171
  try {
3165
- const action = injectMcpJson(cwd, "https://cleargate-mcp.soula.ge/mcp");
3172
+ const action = injectMcpJson(cwd, pinVersion ?? "latest");
3166
3173
  if (action === "created") {
3167
3174
  stdout(
3168
3175
  `[cleargate init] Created .mcp.json (cleargate MCP server registered) \u2014 restart Claude Code to load it.
@@ -8016,7 +8023,7 @@ function extractFrontmatterBlock(raw) {
8016
8023
  // src/lib/intake.ts
8017
8024
  async function runIntakeBranch(opts) {
8018
8025
  const {
8019
- mcp,
8026
+ mcp: mcp2,
8020
8027
  identity,
8021
8028
  sprintRoot,
8022
8029
  projectRoot,
@@ -8027,7 +8034,7 @@ async function runIntakeBranch(opts) {
8027
8034
  const pendingSyncDir = path39.join(projectRoot, ".cleargate", "delivery", "pending-sync");
8028
8035
  let remoteItems = [];
8029
8036
  try {
8030
- remoteItems = await mcp.call(
8037
+ remoteItems = await mcp2.call(
8031
8038
  "cleargate_detect_new_items",
8032
8039
  { label: labelFilter }
8033
8040
  );
@@ -8389,9 +8396,9 @@ async function syncCheckHandler(opts = {}) {
8389
8396
  }
8390
8397
  } catch {
8391
8398
  }
8392
- let mcp;
8399
+ let mcp2;
8393
8400
  if (opts.mcp) {
8394
- mcp = opts.mcp;
8401
+ mcp2 = opts.mcp;
8395
8402
  } else {
8396
8403
  let baseUrl = env["CLEARGATE_MCP_URL"];
8397
8404
  if (!baseUrl || !baseUrl.trim()) {
@@ -8420,10 +8427,10 @@ async function syncCheckHandler(opts = {}) {
8420
8427
  await emitError("adapter-not-configured", nowIso);
8421
8428
  return;
8422
8429
  }
8423
- mcp = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
8430
+ mcp2 = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
8424
8431
  }
8425
8432
  try {
8426
- const adapterInfo = await mcp.adapterInfo();
8433
+ const adapterInfo = await mcp2.adapterInfo();
8427
8434
  if (!adapterInfo.configured || adapterInfo.name === "no-adapter-configured") {
8428
8435
  await emitError("adapter-not-configured", nowIso);
8429
8436
  return;
@@ -8432,7 +8439,7 @@ async function syncCheckHandler(opts = {}) {
8432
8439
  }
8433
8440
  let refs;
8434
8441
  try {
8435
- refs = await mcp.call("cleargate_list_remote_updates", { since });
8442
+ refs = await mcp2.call("cleargate_list_remote_updates", { since });
8436
8443
  } catch (err) {
8437
8444
  const msg = err instanceof Error ? err.message : String(err);
8438
8445
  await emitError(msg, nowIso);
@@ -8452,9 +8459,9 @@ async function syncHandler(opts = {}) {
8452
8459
  const identity = resolveIdentity(projectRoot);
8453
8460
  const sprintRoot = resolveActiveSprintDir(projectRoot);
8454
8461
  const sprintId = path43.basename(sprintRoot);
8455
- let mcp;
8462
+ let mcp2;
8456
8463
  if (opts.mcp) {
8457
- mcp = opts.mcp;
8464
+ mcp2 = opts.mcp;
8458
8465
  } else {
8459
8466
  let baseUrl = env["CLEARGATE_MCP_URL"];
8460
8467
  if (!baseUrl || !baseUrl.trim()) {
@@ -8497,11 +8504,11 @@ async function syncHandler(opts = {}) {
8497
8504
  exit(2);
8498
8505
  return;
8499
8506
  }
8500
- mcp = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
8507
+ mcp2 = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
8501
8508
  }
8502
8509
  let adapterInfo;
8503
8510
  try {
8504
- adapterInfo = await mcp.adapterInfo();
8511
+ adapterInfo = await mcp2.adapterInfo();
8505
8512
  } catch {
8506
8513
  adapterInfo = { configured: true, name: "unknown" };
8507
8514
  }
@@ -8522,13 +8529,13 @@ async function syncHandler(opts = {}) {
8522
8529
  }
8523
8530
  } catch {
8524
8531
  }
8525
- const remoteRefs = await mcp.call(
8532
+ const remoteRefs = await mcp2.call(
8526
8533
  "cleargate_list_remote_updates",
8527
8534
  { since: lastRemoteSync }
8528
8535
  );
8529
8536
  const pulled = [];
8530
8537
  for (const ref of remoteRefs) {
8531
- const item = await mcp.call(
8538
+ const item = await mcp2.call(
8532
8539
  "cleargate_pull_item",
8533
8540
  { remote_id: ref.remote_id }
8534
8541
  );
@@ -8540,7 +8547,7 @@ async function syncHandler(opts = {}) {
8540
8547
  let intakeResult = { created: 0, items: [] };
8541
8548
  try {
8542
8549
  intakeResult = await runIntakeBranch({
8543
- mcp,
8550
+ mcp: mcp2,
8544
8551
  identity,
8545
8552
  sprintRoot,
8546
8553
  projectRoot,
@@ -8566,7 +8573,7 @@ async function syncHandler(opts = {}) {
8566
8573
  const activeSet = await resolveActiveItems(projectRoot, localRefs, nowFn);
8567
8574
  for (const remoteId of activeSet) {
8568
8575
  try {
8569
- const comments = await mcp.call(
8576
+ const comments = await mcp2.call(
8570
8577
  "cleargate_pull_comments",
8571
8578
  { remote_id: remoteId }
8572
8579
  );
@@ -8720,7 +8727,7 @@ async function syncHandler(opts = {}) {
8720
8727
  await appendSyncLog(sprintRoot, entry);
8721
8728
  }
8722
8729
  for (const { localPath, fm, body, itemId } of pushQueue) {
8723
- await mcp.call("push_item", {
8730
+ await mcp2.call("push_item", {
8724
8731
  cleargate_id: itemId,
8725
8732
  type: typeof fm["story_id"] === "string" ? "story" : typeof fm["epic_id"] === "string" ? "epic" : typeof fm["proposal_id"] === "string" ? "proposal" : "story",
8726
8733
  payload: fm
@@ -8853,9 +8860,9 @@ async function pullHandler(idOrRemoteId, opts = {}) {
8853
8860
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
8854
8861
  const identity = resolveIdentity(projectRoot);
8855
8862
  const sprintRoot = resolveActiveSprintDir(projectRoot);
8856
- let mcp;
8863
+ let mcp2;
8857
8864
  if (opts.mcp) {
8858
- mcp = opts.mcp;
8865
+ mcp2 = opts.mcp;
8859
8866
  } else {
8860
8867
  let baseUrl = env["CLEARGATE_MCP_URL"];
8861
8868
  if (!baseUrl || !baseUrl.trim()) {
@@ -8890,7 +8897,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
8890
8897
  exit(2);
8891
8898
  return;
8892
8899
  }
8893
- mcp = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
8900
+ mcp2 = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
8894
8901
  }
8895
8902
  const remoteId = await resolveRemoteId(idOrRemoteId, projectRoot);
8896
8903
  if (!remoteId) {
@@ -8899,7 +8906,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
8899
8906
  exit(1);
8900
8907
  return;
8901
8908
  }
8902
- const remoteItem = await mcp.call("cleargate_pull_item", { remote_id: remoteId });
8909
+ const remoteItem = await mcp2.call("cleargate_pull_item", { remote_id: remoteId });
8903
8910
  if (!remoteItem) {
8904
8911
  stderr(`Error: item ${remoteId} not found on MCP server.
8905
8912
  `);
@@ -8959,7 +8966,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
8959
8966
  stdout(`pull: ${remoteId} applied to ${path44.relative(projectRoot, localPath)}
8960
8967
  `);
8961
8968
  if (opts.comments) {
8962
- const comments = await mcp.call(
8969
+ const comments = await mcp2.call(
8963
8970
  "cleargate_pull_comments",
8964
8971
  { remote_id: remoteId }
8965
8972
  );
@@ -9157,10 +9164,10 @@ async function handlePush(filePath, ctx) {
9157
9164
  if (h1) payloadForPush["title"] = h1;
9158
9165
  }
9159
9166
  payloadForPush["body"] = body;
9160
- const mcp = await resolveMcp();
9167
+ const mcp2 = await resolveMcp();
9161
9168
  let result;
9162
9169
  try {
9163
- result = await mcp.call("push_item", {
9170
+ result = await mcp2.call("push_item", {
9164
9171
  cleargate_id: itemId,
9165
9172
  type,
9166
9173
  payload: payloadForPush,
@@ -9212,9 +9219,9 @@ async function handleRevert(idOrRemoteId, ctx) {
9212
9219
  exit(1);
9213
9220
  return;
9214
9221
  }
9215
- const mcp = await resolveMcp();
9222
+ const mcp2 = await resolveMcp();
9216
9223
  try {
9217
- await mcp.call("sync_status", {
9224
+ await mcp2.call("sync_status", {
9218
9225
  cleargate_id: itemId,
9219
9226
  new_status: "archived-without-shipping"
9220
9227
  });
@@ -9653,6 +9660,218 @@ function hotfixNewHandler(opts, cli) {
9653
9660
  return exitFn(0);
9654
9661
  }
9655
9662
 
9663
+ // src/commands/mcp-serve.ts
9664
+ init_cjs_shims();
9665
+ var readline5 = __toESM(require("readline"), 1);
9666
+ init_config();
9667
+ init_factory();
9668
+
9669
+ // src/auth/refresh.ts
9670
+ init_cjs_shims();
9671
+ async function refreshAccessToken(baseUrl, refreshToken, deps = {}) {
9672
+ const fetchFn = deps.fetch ?? globalThis.fetch;
9673
+ const res = await fetchFn(`${baseUrl}/auth/refresh`, {
9674
+ method: "POST",
9675
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
9676
+ body: JSON.stringify({ refresh_token: refreshToken })
9677
+ });
9678
+ if (!res.ok) {
9679
+ const body = await res.json().catch(() => ({}));
9680
+ throw new RefreshError(res.status, body.error ?? "unknown_error");
9681
+ }
9682
+ const json = await res.json();
9683
+ if (typeof json.access_token !== "string" || typeof json.refresh_token !== "string" || typeof json.expires_in !== "number") {
9684
+ throw new RefreshError(500, "malformed_response");
9685
+ }
9686
+ return json;
9687
+ }
9688
+ var RefreshError = class extends Error {
9689
+ constructor(status, code) {
9690
+ super(`refresh failed: ${status} ${code}`);
9691
+ this.status = status;
9692
+ this.code = code;
9693
+ this.name = "RefreshError";
9694
+ }
9695
+ status;
9696
+ code;
9697
+ };
9698
+ var AuthFetcher = class {
9699
+ constructor(opts) {
9700
+ this.opts = opts;
9701
+ }
9702
+ opts;
9703
+ accessToken = null;
9704
+ accessExpiresAt = 0;
9705
+ inflight = null;
9706
+ /** Returns a fresh access token, refreshing if needed. */
9707
+ async getAccessToken() {
9708
+ const now = (this.opts.now ?? (() => Date.now()))();
9709
+ const skewMs = (this.opts.skewSeconds ?? 60) * 1e3;
9710
+ if (this.accessToken && now < this.accessExpiresAt - skewMs) {
9711
+ return this.accessToken;
9712
+ }
9713
+ if (this.inflight) return this.inflight;
9714
+ this.inflight = this.refreshNow().finally(() => {
9715
+ this.inflight = null;
9716
+ });
9717
+ return this.inflight;
9718
+ }
9719
+ /** Force the next call to refresh. Used after a 401. */
9720
+ invalidate() {
9721
+ this.accessToken = null;
9722
+ this.accessExpiresAt = 0;
9723
+ }
9724
+ async refreshNow() {
9725
+ const stored = await this.opts.loadRefresh();
9726
+ if (!stored) {
9727
+ throw new RefreshError(401, "no_refresh_token");
9728
+ }
9729
+ const exchanged = await refreshAccessToken(this.opts.baseUrl, stored, {
9730
+ ...this.opts.fetch ? { fetch: this.opts.fetch } : {},
9731
+ ...this.opts.now ? { now: this.opts.now } : {}
9732
+ });
9733
+ await this.opts.saveRefresh(exchanged.refresh_token);
9734
+ const now = (this.opts.now ?? (() => Date.now()))();
9735
+ this.accessToken = exchanged.access_token;
9736
+ this.accessExpiresAt = now + exchanged.expires_in * 1e3;
9737
+ return exchanged.access_token;
9738
+ }
9739
+ };
9740
+
9741
+ // src/commands/mcp-serve.ts
9742
+ var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
9743
+ async function mcpServeHandler(opts) {
9744
+ const fetchFn = opts.fetch ?? globalThis.fetch;
9745
+ const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
9746
+ const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
9747
+ const exit = opts.exit ?? ((c) => process.exit(c));
9748
+ const cfg = loadConfig({
9749
+ flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
9750
+ });
9751
+ const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
9752
+ const store = await (opts.createStore ?? createTokenStore)({
9753
+ ...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
9754
+ ...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
9755
+ });
9756
+ const fetcher = new AuthFetcher({
9757
+ baseUrl,
9758
+ loadRefresh: () => store.load(opts.profile),
9759
+ saveRefresh: (t) => store.save(opts.profile, t),
9760
+ ...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
9761
+ ...opts.now !== void 0 ? { now: opts.now } : {}
9762
+ });
9763
+ try {
9764
+ await fetcher.getAccessToken();
9765
+ } catch (err) {
9766
+ if (err instanceof RefreshError) {
9767
+ stderr(
9768
+ `cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
9769
+ `
9770
+ );
9771
+ } else {
9772
+ stderr(
9773
+ `cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
9774
+ `
9775
+ );
9776
+ }
9777
+ return exit(1);
9778
+ }
9779
+ const inputStream = opts.stdin ?? process.stdin;
9780
+ const rl = readline5.createInterface({
9781
+ input: inputStream,
9782
+ output: void 0,
9783
+ terminal: false
9784
+ });
9785
+ for await (const line of rl) {
9786
+ if (!line.trim()) continue;
9787
+ try {
9788
+ await proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr);
9789
+ } catch (err) {
9790
+ const errMsg = err instanceof Error ? err.message : String(err);
9791
+ stderr(`cleargate mcp serve: proxy error: ${errMsg}
9792
+ `);
9793
+ const id = extractId(line);
9794
+ if (id !== void 0) {
9795
+ stdout(
9796
+ JSON.stringify({
9797
+ jsonrpc: "2.0",
9798
+ id,
9799
+ error: { code: -32603, message: `proxy error: ${errMsg}` }
9800
+ }) + "\n"
9801
+ );
9802
+ }
9803
+ }
9804
+ }
9805
+ }
9806
+ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
9807
+ let parsed;
9808
+ try {
9809
+ parsed = JSON.parse(line);
9810
+ } catch {
9811
+ stderr(`cleargate mcp serve: ignoring non-JSON line: ${line.slice(0, 80)}
9812
+ `);
9813
+ return;
9814
+ }
9815
+ const isNotification = !("id" in parsed) || parsed.id === void 0 || parsed.id === null;
9816
+ let access = await fetcher.getAccessToken();
9817
+ let res = await postFrame(baseUrl, line, access, fetchFn);
9818
+ if (res.status === 401) {
9819
+ fetcher.invalidate();
9820
+ access = await fetcher.getAccessToken();
9821
+ res = await postFrame(baseUrl, line, access, fetchFn);
9822
+ }
9823
+ if (isNotification) {
9824
+ await res.arrayBuffer().catch(() => void 0);
9825
+ return;
9826
+ }
9827
+ const ct = res.headers.get("content-type") ?? "";
9828
+ if (ct.includes("text/event-stream")) {
9829
+ await streamSse(res, stdout);
9830
+ } else {
9831
+ const text = await res.text();
9832
+ if (text.length > 0) stdout(text + "\n");
9833
+ }
9834
+ }
9835
+ async function postFrame(baseUrl, body, accessToken, fetchFn) {
9836
+ return fetchFn(`${baseUrl}/mcp`, {
9837
+ method: "POST",
9838
+ headers: {
9839
+ "Content-Type": "application/json",
9840
+ Accept: "application/json, text/event-stream",
9841
+ Authorization: `Bearer ${accessToken}`
9842
+ },
9843
+ body
9844
+ });
9845
+ }
9846
+ async function streamSse(res, stdout) {
9847
+ if (!res.body) return;
9848
+ const reader = res.body.getReader();
9849
+ const decoder = new TextDecoder("utf-8");
9850
+ let buf = "";
9851
+ for (; ; ) {
9852
+ const { value, done } = await reader.read();
9853
+ if (done) break;
9854
+ buf += decoder.decode(value, { stream: true });
9855
+ let nl;
9856
+ while ((nl = buf.indexOf("\n")) !== -1) {
9857
+ const ln = buf.slice(0, nl);
9858
+ buf = buf.slice(nl + 1);
9859
+ if (ln.startsWith("data:")) {
9860
+ const payload = ln.slice(5).trim();
9861
+ if (payload) stdout(payload + "\n");
9862
+ }
9863
+ }
9864
+ }
9865
+ }
9866
+ function extractId(line) {
9867
+ try {
9868
+ const obj = JSON.parse(line);
9869
+ return "id" in obj ? obj.id : void 0;
9870
+ } catch {
9871
+ return void 0;
9872
+ }
9873
+ }
9874
+
9656
9875
  // src/cli.ts
9657
9876
  var program = new import_commander.Command();
9658
9877
  program.name("cleargate").description("ClearGate CLI \u2014 connects AI agent teams to the ClearGate MCP server").version(package_default.version, "-V, --version").option("--profile <name>", "configuration profile to use", "default").option("--mcp-url <url>", "MCP server URL (overrides config file and env)").showHelpAfterError("(use `cleargate --help`)");
@@ -9895,5 +10114,13 @@ var hotfix = program.command("hotfix").description("hotfix lane commands (off-sp
9895
10114
  hotfix.command("new <slug>").description("scaffold a new HOTFIX-NNN_<slug>.md in pending-sync/").action((slug) => {
9896
10115
  hotfixNewHandler({ slug });
9897
10116
  });
10117
+ var mcp = program.command("mcp").description("MCP-server bridge commands (stdio shim, registration helpers)");
10118
+ mcp.command("serve").description("run a stdio MCP server that proxies to the cleargate HTTP /mcp endpoint with auto-refresh Bearer auth").action(async (_opts, command) => {
10119
+ const globals = command.parent.parent.opts();
10120
+ await mcpServeHandler({
10121
+ profile: globals.profile,
10122
+ ...globals.mcpUrl !== void 0 ? { mcpUrlFlag: globals.mcpUrl } : {}
10123
+ });
10124
+ });
9898
10125
  void program.parseAsync(process.argv);
9899
10126
  //# sourceMappingURL=cli.cjs.map