cleargate 0.7.0 → 0.8.0

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