cleargate 0.11.4 → 0.12.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.
package/dist/cli.cjs CHANGED
@@ -114,6 +114,40 @@ function requireMcpUrl(cfg) {
114
114
  }
115
115
  return cfg.mcpUrl;
116
116
  }
117
+ function saveConfig(updates, opts = {}) {
118
+ const home = os.homedir();
119
+ if (!home) {
120
+ throw new Error("Cannot determine home directory.");
121
+ }
122
+ const configPath = opts.configPath ?? path2.join(home, ".cleargate", "config.json");
123
+ const dir = path2.dirname(configPath);
124
+ let existing = {};
125
+ try {
126
+ const raw = fs2.readFileSync(configPath, "utf8");
127
+ const parsed = JSON.parse(raw);
128
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
129
+ existing = parsed;
130
+ }
131
+ } catch (err) {
132
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
133
+ }
134
+ }
135
+ const merged = { ...existing };
136
+ for (const [k, v] of Object.entries(updates)) {
137
+ if (v !== void 0) merged[k] = v;
138
+ }
139
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
140
+ try {
141
+ fs2.chmodSync(dir, 448);
142
+ } catch {
143
+ }
144
+ const tmpPath = path2.join(dir, ".config.json.tmp");
145
+ const json = JSON.stringify(merged, null, 2) + "\n";
146
+ fs2.writeFileSync(tmpPath, json, { mode: 384 });
147
+ fs2.chmodSync(tmpPath, 384);
148
+ fs2.renameSync(tmpPath, configPath);
149
+ fs2.chmodSync(configPath, 384);
150
+ }
117
151
  var fs2, os, path2, import_zod, ConfigSchema;
118
152
  var init_config = __esm({
119
153
  "src/config.ts"() {
@@ -383,6 +417,41 @@ var init_membership = __esm({
383
417
  });
384
418
 
385
419
  // src/auth/acquire.ts
420
+ function defaultDiskCachePath(env = process.env) {
421
+ const override = env["CLEARGATE_DISK_CACHE_PATH"];
422
+ if (override === "off") return null;
423
+ if (typeof override === "string" && override.length > 0) return override;
424
+ const home = os9.homedir();
425
+ if (!home) return null;
426
+ return path45.join(home, ".cleargate", "access-token.json");
427
+ }
428
+ function readDiskCache(filePath) {
429
+ try {
430
+ const raw = fs42.readFileSync(filePath, "utf8");
431
+ const parsed = JSON.parse(raw);
432
+ if (parsed !== null && typeof parsed === "object" && parsed.version === 1 && typeof parsed.entries === "object" && parsed.entries !== null) {
433
+ return parsed;
434
+ }
435
+ } catch {
436
+ }
437
+ return { version: 1, entries: {} };
438
+ }
439
+ function writeDiskCache(filePath, data) {
440
+ const dir = path45.dirname(filePath);
441
+ try {
442
+ fs42.mkdirSync(dir, { recursive: true, mode: 448 });
443
+ try {
444
+ fs42.chmodSync(dir, 448);
445
+ } catch {
446
+ }
447
+ const tmpPath = path45.join(dir, ".access-token.json.tmp");
448
+ fs42.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
449
+ fs42.chmodSync(tmpPath, 384);
450
+ fs42.renameSync(tmpPath, filePath);
451
+ fs42.chmodSync(filePath, 384);
452
+ } catch {
453
+ }
454
+ }
386
455
  function decodeJwtPayload2(token) {
387
456
  try {
388
457
  const parts = token.split(".");
@@ -408,6 +477,15 @@ async function acquireAccessToken(opts) {
408
477
  return cached.accessToken;
409
478
  }
410
479
  }
480
+ const diskCachePath = opts.diskCachePath === void 0 ? defaultDiskCachePath() : opts.diskCachePath;
481
+ if (!opts.forceRefresh && diskCachePath) {
482
+ const file = readDiskCache(diskCachePath);
483
+ const entry = file.entries[cacheKey];
484
+ if (entry && nowFn() < entry.expiresAtMs) {
485
+ CACHE.set(cacheKey, entry);
486
+ return entry.accessToken;
487
+ }
488
+ }
411
489
  const store = await (opts.createStore ?? createTokenStore)();
412
490
  const stored = await store.load(opts.profile);
413
491
  if (!stored) {
@@ -456,15 +534,24 @@ async function acquireAccessToken(opts) {
456
534
  const exp = payload?.exp;
457
535
  if (typeof exp === "number" && Number.isFinite(exp)) {
458
536
  const expiresAtMs = (exp - 60) * 1e3;
459
- CACHE.set(cacheKey, { accessToken, expiresAtMs });
537
+ const entry = { accessToken, expiresAtMs };
538
+ CACHE.set(cacheKey, entry);
539
+ if (diskCachePath) {
540
+ const file = readDiskCache(diskCachePath);
541
+ file.entries[cacheKey] = entry;
542
+ writeDiskCache(diskCachePath, file);
543
+ }
460
544
  }
461
545
  return accessToken;
462
546
  }
463
- var CACHE, AcquireError;
547
+ var fs42, os9, path45, CACHE, AcquireError;
464
548
  var init_acquire = __esm({
465
549
  "src/auth/acquire.ts"() {
466
550
  "use strict";
467
551
  init_cjs_shims();
552
+ fs42 = __toESM(require("fs"), 1);
553
+ os9 = __toESM(require("os"), 1);
554
+ path45 = __toESM(require("path"), 1);
468
555
  init_factory();
469
556
  CACHE = /* @__PURE__ */ new Map();
470
557
  AcquireError = class extends Error {
@@ -696,7 +783,7 @@ var import_commander = require("commander");
696
783
  // package.json
697
784
  var package_default = {
698
785
  name: "cleargate",
699
- version: "0.11.4",
786
+ version: "0.12.0",
700
787
  private: false,
701
788
  type: "module",
702
789
  description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, five-role agent team (architect/developer/qa/devops/reporter), Karpathy-style awareness wiki.",
@@ -1314,7 +1401,8 @@ async function joinHandler(opts) {
1314
1401
  if (UUID_V4_RE.test(opts.inviteUrl)) {
1315
1402
  token = opts.inviteUrl;
1316
1403
  const cfg = loadConfig({
1317
- flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
1404
+ flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag },
1405
+ ...opts.configPath !== void 0 ? { configPath: opts.configPath } : {}
1318
1406
  });
1319
1407
  if (!cfg.mcpUrl) {
1320
1408
  stderr(
@@ -1651,9 +1739,15 @@ async function joinHandler(opts) {
1651
1739
  try {
1652
1740
  const store = await (opts.createStore ?? createTokenStore)();
1653
1741
  await store.save(opts.profile, refreshToken);
1742
+ saveConfig(
1743
+ { mcpUrl: baseUrl },
1744
+ opts.configPath !== void 0 ? { configPath: opts.configPath } : {}
1745
+ );
1654
1746
  stdout(`joined project '${projectName}' as '${hostname3()}'
1655
1747
  `);
1656
1748
  stdout(`refresh token saved to ${store.backend}.
1749
+ `);
1750
+ stdout(`mcp_url ${baseUrl} saved to ~/.cleargate/config.json.
1657
1751
  `);
1658
1752
  } catch (err) {
1659
1753
  stderr(
@@ -2210,6 +2304,24 @@ var PREFIX_MAP = [
2210
2304
  { prefix: "BUG-", type: "bug", bucket: "bugs" },
2211
2305
  { prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
2212
2306
  ];
2307
+ var SPRINT_REPORT_FILENAMES = ["REPORT.md"];
2308
+ var SPRINT_REPORT_CANONICAL_RE = /^SPRINT-\d{2,}_REPORT\.md$/;
2309
+ function isSprintReportPath(relPath) {
2310
+ const normalised = relPath.replace(/\\/g, "/");
2311
+ const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\/([^/]+)$/.exec(normalised);
2312
+ if (!match) return false;
2313
+ const filename = match[2];
2314
+ return SPRINT_REPORT_FILENAMES.includes(filename) || SPRINT_REPORT_CANONICAL_RE.test(filename);
2315
+ }
2316
+ function deriveBucketFromReportPath(relPath) {
2317
+ const normalised = relPath.replace(/\\/g, "/");
2318
+ const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\//.exec(normalised);
2319
+ if (!match) {
2320
+ throw new Error(`deriveBucketFromReportPath: cannot extract SPRINT-NN from: ${relPath}`);
2321
+ }
2322
+ const id = match[1];
2323
+ return { type: "sprint", id, bucket: "sprints" };
2324
+ }
2213
2325
  function deriveBucket(filename) {
2214
2326
  const base = filename.includes("/") ? filename.split("/").pop() : filename;
2215
2327
  const stem = base.endsWith(".md") ? base.slice(0, -3) : base;
@@ -2333,6 +2445,12 @@ function serializePage(page, body) {
2333
2445
  if (page.sprint_cleargate_id !== void 0) {
2334
2446
  lines.push(`sprint_cleargate_id: "${page.sprint_cleargate_id}"`);
2335
2447
  }
2448
+ if (page.report_raw_path !== void 0) {
2449
+ lines.push(`report_raw_path: "${page.report_raw_path}"`);
2450
+ }
2451
+ if (page.last_report_ingest_commit !== void 0) {
2452
+ lines.push(`last_report_ingest_commit: "${page.last_report_ingest_commit}"`);
2453
+ }
2336
2454
  lines.push("---");
2337
2455
  const fm = lines.join("\n");
2338
2456
  return `${fm}
@@ -2355,7 +2473,9 @@ function parsePage(raw) {
2355
2473
  const last_contradict_sha = fm["last_contradict_sha"] !== void 0 ? String(fm["last_contradict_sha"]) : void 0;
2356
2474
  const parent_cleargate_id = fm["parent_cleargate_id"] !== void 0 ? String(fm["parent_cleargate_id"]) : void 0;
2357
2475
  const sprint_cleargate_id = fm["sprint_cleargate_id"] !== void 0 ? String(fm["sprint_cleargate_id"]) : void 0;
2358
- return { type, id, parent, children, status, remote_id, raw_path, last_ingest, last_ingest_commit, repo, last_contradict_sha, parent_cleargate_id, sprint_cleargate_id };
2476
+ const report_raw_path = fm["report_raw_path"] !== void 0 ? String(fm["report_raw_path"]) : void 0;
2477
+ const last_report_ingest_commit = fm["last_report_ingest_commit"] !== void 0 ? String(fm["last_report_ingest_commit"]) : void 0;
2478
+ return { type, id, parent, children, status, remote_id, raw_path, last_ingest, last_ingest_commit, repo, last_contradict_sha, parent_cleargate_id, sprint_cleargate_id, report_raw_path, last_report_ingest_commit };
2359
2479
  }
2360
2480
  function parseFmRaw(raw) {
2361
2481
  const lines = raw.split("\n");
@@ -3887,28 +4007,33 @@ async function wikiIngestHandler(opts) {
3887
4007
  const rawPath = opts.rawPath;
3888
4008
  const absRawPath = path20.isAbsolute(rawPath) ? rawPath : path20.resolve(cwd, rawPath);
3889
4009
  const relRawPath = path20.relative(cwd, absRawPath).replace(/\\/g, "/");
3890
- const deliveryRoot = path20.join(cwd, ".cleargate", "delivery");
3891
- const deliveryRootNorm = deliveryRoot.replace(/\\/g, "/");
3892
- const absDeliveryRoot = deliveryRoot;
3893
- const relToDelivery = path20.relative(absDeliveryRoot, absRawPath);
3894
- if (relToDelivery.startsWith("..") || path20.isAbsolute(relToDelivery)) {
3895
- stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
4010
+ const isSprintReport = isSprintReportPath(relRawPath);
4011
+ if (!isSprintReport) {
4012
+ const deliveryRoot = path20.join(cwd, ".cleargate", "delivery");
4013
+ const absDeliveryRoot = deliveryRoot;
4014
+ const relToDelivery = path20.relative(absDeliveryRoot, absRawPath);
4015
+ if (relToDelivery.startsWith("..") || path20.isAbsolute(relToDelivery)) {
4016
+ stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
3896
4017
  `);
3897
- exit(2);
3898
- return;
3899
- }
3900
- void deliveryRootNorm;
3901
- const isExcluded = EXCLUDED_SUFFIXES2.some((excl) => relRawPath.startsWith(excl));
3902
- if (isExcluded) {
3903
- stdout(`wiki ingest: ${rawPath} excluded (skip)
4018
+ exit(2);
4019
+ return;
4020
+ }
4021
+ const isExcluded = EXCLUDED_SUFFIXES2.some((excl) => relRawPath.startsWith(excl));
4022
+ if (isExcluded) {
4023
+ stdout(`wiki ingest: ${rawPath} excluded (skip)
3904
4024
  `);
3905
- exit(0);
3906
- return;
4025
+ exit(0);
4026
+ return;
4027
+ }
3907
4028
  }
3908
4029
  const filename = path20.basename(absRawPath);
3909
4030
  let bucketInfo;
3910
4031
  try {
3911
- bucketInfo = deriveBucket(filename);
4032
+ if (isSprintReport) {
4033
+ bucketInfo = deriveBucketFromReportPath(relRawPath);
4034
+ } else {
4035
+ bucketInfo = deriveBucket(filename);
4036
+ }
3912
4037
  } catch (e) {
3913
4038
  stderr(`wiki ingest: cannot determine bucket for ${rawPath}: ${e.message}
3914
4039
  `);
@@ -3951,16 +4076,26 @@ async function wikiIngestHandler(opts) {
3951
4076
  }
3952
4077
  const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
3953
4078
  const pageExists = fs19.existsSync(pagePath);
3954
- if (pageExists && currentSha !== "") {
3955
- let isNoOp = false;
4079
+ let existingPage;
4080
+ if (pageExists) {
3956
4081
  try {
3957
4082
  const existingPageContent = fs19.readFileSync(pagePath, "utf8");
3958
- const existingPage = parsePage(existingPageContent);
3959
- if (existingPage.last_ingest_commit === currentSha) {
3960
- const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
3961
- if (contentUnchanged) {
4083
+ existingPage = parsePage(existingPageContent);
4084
+ } catch {
4085
+ }
4086
+ }
4087
+ if (existingPage !== void 0 && currentSha !== "") {
4088
+ let isNoOp = false;
4089
+ try {
4090
+ if (isSprintReport) {
4091
+ if (existingPage.last_report_ingest_commit === currentSha) {
3962
4092
  isNoOp = true;
3963
4093
  }
4094
+ } else {
4095
+ if (existingPage.last_ingest_commit === currentSha) {
4096
+ const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
4097
+ if (contentUnchanged) isNoOp = true;
4098
+ }
3964
4099
  }
3965
4100
  } catch {
3966
4101
  }
@@ -3972,36 +4107,53 @@ async function wikiIngestHandler(opts) {
3972
4107
  }
3973
4108
  }
3974
4109
  const action = pageExists ? "update" : "create";
3975
- let existingLastContradictSha;
3976
- if (pageExists) {
3977
- try {
3978
- const existingPageContent = fs19.readFileSync(pagePath, "utf8");
3979
- const existingPage = parsePage(existingPageContent);
3980
- existingLastContradictSha = existingPage.last_contradict_sha;
3981
- } catch {
3982
- }
3983
- }
4110
+ const timestamp = now();
4111
+ const existingLastContradictSha = existingPage?.last_contradict_sha;
4112
+ const existingReportRawPath = existingPage?.report_raw_path;
4113
+ const existingLastReportIngestCommit = existingPage?.last_report_ingest_commit;
3984
4114
  const parent = buildParentRef2(fm);
3985
4115
  const children = buildChildrenRefs2(fm);
3986
- const timestamp = now();
3987
4116
  const wikiPage = {
3988
4117
  type,
3989
4118
  id,
3990
- parent,
3991
- children,
3992
- status: String(fm["status"] ?? ""),
3993
- remote_id: String(fm["remote_id"] ?? ""),
3994
- raw_path: relRawPath,
4119
+ parent: isSprintReport ? existingPage?.parent ?? "" : parent,
4120
+ children: isSprintReport ? existingPage?.children ?? [] : children,
4121
+ status: isSprintReport ? existingPage?.status ?? String(fm["status"] ?? "") : String(fm["status"] ?? ""),
4122
+ remote_id: isSprintReport ? existingPage?.remote_id ?? String(fm["remote_id"] ?? "") : String(fm["remote_id"] ?? ""),
4123
+ // raw_path tracks the plan file path; for report-only ingest preserve existing or use relRawPath as fallback
4124
+ raw_path: isSprintReport ? existingPage?.raw_path ?? relRawPath : relRawPath,
3995
4125
  last_ingest: timestamp,
3996
- last_ingest_commit: currentSha,
4126
+ // last_ingest_commit tracks the plan source; preserve when re-ingesting report
4127
+ last_ingest_commit: isSprintReport ? existingPage?.last_ingest_commit ?? "" : currentSha,
3997
4128
  repo,
3998
4129
  // Carry forward last_contradict_sha so Phase 4 idempotency survives re-ingest
3999
4130
  ...existingLastContradictSha !== void 0 ? { last_contradict_sha: existingLastContradictSha } : {},
4000
- // Hierarchy keys (§11.7): read from raw fm stamped at raw-side, not wiki-side
4001
- ...typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
4002
- ...typeof fm["sprint_cleargate_id"] === "string" ? { sprint_cleargate_id: fm["sprint_cleargate_id"] } : {}
4131
+ // Hierarchy keys (§11.7): read from raw fm for plan ingest; preserve for report ingest
4132
+ ...isSprintReport ? existingPage?.parent_cleargate_id !== void 0 ? { parent_cleargate_id: existingPage.parent_cleargate_id } : {} : typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
4133
+ ...isSprintReport ? existingPage?.sprint_cleargate_id !== void 0 ? { sprint_cleargate_id: existingPage.sprint_cleargate_id } : {} : typeof fm["sprint_cleargate_id"] === "string" ? { sprint_cleargate_id: fm["sprint_cleargate_id"] } : {},
4134
+ // CR-063 sprint-report fields
4135
+ report_raw_path: isSprintReport ? relRawPath : existingReportRawPath ?? void 0,
4136
+ last_report_ingest_commit: isSprintReport ? currentSha : existingLastReportIngestCommit ?? void 0
4003
4137
  };
4004
- const pageBody = buildPageBody2({ id, fm, body });
4138
+ let existingPageBody = "";
4139
+ if (existingPage !== void 0 && pageExists) {
4140
+ try {
4141
+ const existingPageContent = fs19.readFileSync(pagePath, "utf8");
4142
+ const lines = existingPageContent.split("\n");
4143
+ let closingDash = -1;
4144
+ for (let i = 1; i < lines.length; i++) {
4145
+ if (lines[i] === "---") {
4146
+ closingDash = i;
4147
+ break;
4148
+ }
4149
+ }
4150
+ if (closingDash !== -1) {
4151
+ existingPageBody = lines.slice(closingDash + 1).join("\n").replace(/^\n/, "");
4152
+ }
4153
+ } catch {
4154
+ }
4155
+ }
4156
+ const pageBody = buildPageBody2({ id, fm, body, isSprintReport, existingPageBody });
4005
4157
  const pageContent = serializePage(wikiPage, pageBody);
4006
4158
  fs19.mkdirSync(pageDir, { recursive: true });
4007
4159
  fs19.writeFileSync(pagePath, pageContent, "utf8");
@@ -4079,7 +4231,37 @@ function buildChildrenRefs2(fm) {
4079
4231
  return `[[${s}]]`;
4080
4232
  });
4081
4233
  }
4234
+ function extractReportBlock(pageBody) {
4235
+ const beginMarker = "<!-- BEGIN sprint-report -->";
4236
+ const endMarker = "<!-- END sprint-report -->";
4237
+ const beginIdx = pageBody.indexOf(beginMarker);
4238
+ const endIdx = pageBody.indexOf(endMarker);
4239
+ if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) return void 0;
4240
+ return pageBody.slice(beginIdx, endIdx + endMarker.length);
4241
+ }
4242
+ function extractPlanStub(pageBody) {
4243
+ const beginMarker = "<!-- BEGIN sprint-report -->";
4244
+ const beginIdx = pageBody.indexOf(beginMarker);
4245
+ if (beginIdx === -1) return pageBody;
4246
+ return pageBody.slice(0, beginIdx);
4247
+ }
4082
4248
  function buildPageBody2(item) {
4249
+ const { isSprintReport, existingPageBody } = item;
4250
+ if (isSprintReport) {
4251
+ const planStub = existingPageBody ? extractPlanStub(existingPageBody) : buildPlanStub(item);
4252
+ const reportBlock = buildReportBlock(item.body);
4253
+ return planStub + reportBlock;
4254
+ } else {
4255
+ const planStub = buildPlanStub(item);
4256
+ const existingReportBlock = existingPageBody ? extractReportBlock(existingPageBody) : void 0;
4257
+ if (existingReportBlock !== void 0) {
4258
+ const stub = planStub.trimEnd() + "\n\n";
4259
+ return stub + existingReportBlock + "\n";
4260
+ }
4261
+ return planStub;
4262
+ }
4263
+ }
4264
+ function buildPlanStub(item) {
4083
4265
  const title = String(item.fm["title"] ?? item.id);
4084
4266
  const summary = String(
4085
4267
  item.fm["description"] ?? item.body.split("\n")[0] ?? "No summary available."
@@ -4103,6 +4285,16 @@ function buildPageBody2(item) {
4103
4285
  ""
4104
4286
  ].join("\n");
4105
4287
  }
4288
+ function buildReportBlock(reportBody) {
4289
+ return [
4290
+ "<!-- BEGIN sprint-report -->",
4291
+ "## Sprint Report",
4292
+ "",
4293
+ reportBody.trim(),
4294
+ "<!-- END sprint-report -->",
4295
+ ""
4296
+ ].join("\n");
4297
+ }
4106
4298
  function appendLogEntry(wikiRoot, entry) {
4107
4299
  const logPath = path20.join(wikiRoot, "log.md");
4108
4300
  const logEntry = [
@@ -6583,7 +6775,7 @@ async function writeCachedGate(absPath, result, opts) {
6583
6775
  throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
6584
6776
  }
6585
6777
  const existing = coerceCachedGate(fm["cached_gate_result"]);
6586
- if (existing && JSON.stringify(existing) === JSON.stringify(newResult)) {
6778
+ if (existing && existing.pass === newResult.pass && JSON.stringify(existing.failing_criteria) === JSON.stringify(newResult.failing_criteria)) {
6587
6779
  return;
6588
6780
  }
6589
6781
  const newFm = {};
@@ -8109,6 +8301,10 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
8109
8301
  if (!absPath) {
8110
8302
  continue;
8111
8303
  }
8304
+ if (absPath.includes(`${path36.sep}archive${path36.sep}`)) {
8305
+ result.skipped.push(id);
8306
+ continue;
8307
+ }
8112
8308
  let status = "";
8113
8309
  try {
8114
8310
  const raw = fs34.readFileSync(absPath, "utf8");
@@ -9618,7 +9814,7 @@ async function uninstallHandler(opts) {
9618
9814
  // src/commands/sync.ts
9619
9815
  init_cjs_shims();
9620
9816
  var fsPromises8 = __toESM(require("fs/promises"), 1);
9621
- var path50 = __toESM(require("path"), 1);
9817
+ var path51 = __toESM(require("path"), 1);
9622
9818
 
9623
9819
  // src/lib/sync-log.ts
9624
9820
  init_cjs_shims();
@@ -9938,12 +10134,12 @@ init_config();
9938
10134
  // src/lib/intake.ts
9939
10135
  init_cjs_shims();
9940
10136
  var fsPromises4 = __toESM(require("fs/promises"), 1);
9941
- var path46 = __toESM(require("path"), 1);
10137
+ var path47 = __toESM(require("path"), 1);
9942
10138
 
9943
10139
  // src/lib/slug.ts
9944
10140
  init_cjs_shims();
9945
10141
  var fsPromises3 = __toESM(require("fs/promises"), 1);
9946
- var path45 = __toESM(require("path"), 1);
10142
+ var path46 = __toESM(require("path"), 1);
9947
10143
  function slugify(title, max = 40) {
9948
10144
  const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
9949
10145
  const lowered = normalized.toLowerCase();
@@ -9958,8 +10154,8 @@ function slugify(title, max = 40) {
9958
10154
  var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
9959
10155
  async function nextProposalId(projectRoot) {
9960
10156
  const dirs = [
9961
- path45.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
9962
- path45.join(projectRoot, ".cleargate", "delivery", "archive")
10157
+ path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10158
+ path46.join(projectRoot, ".cleargate", "delivery", "archive")
9963
10159
  ];
9964
10160
  let maxN = 0;
9965
10161
  for (const dir of dirs) {
@@ -9971,7 +10167,7 @@ async function nextProposalId(projectRoot) {
9971
10167
  }
9972
10168
  for (const entry of entries) {
9973
10169
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
9974
- const fullPath = path45.join(dir, entry.name);
10170
+ const fullPath = path46.join(dir, entry.name);
9975
10171
  try {
9976
10172
  const raw = await fsPromises3.readFile(fullPath, "utf8");
9977
10173
  const fmEnd = extractFrontmatterBlock(raw);
@@ -9989,8 +10185,8 @@ async function nextProposalId(projectRoot) {
9989
10185
  }
9990
10186
  async function findByRemoteId(projectRoot, remoteId) {
9991
10187
  const dirs = [
9992
- path45.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
9993
- path45.join(projectRoot, ".cleargate", "delivery", "archive")
10188
+ path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10189
+ path46.join(projectRoot, ".cleargate", "delivery", "archive")
9994
10190
  ];
9995
10191
  const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9996
10192
  const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
@@ -10003,7 +10199,7 @@ async function findByRemoteId(projectRoot, remoteId) {
10003
10199
  }
10004
10200
  for (const entry of entries) {
10005
10201
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10006
- const fullPath = path45.join(dir, entry.name);
10202
+ const fullPath = path46.join(dir, entry.name);
10007
10203
  try {
10008
10204
  const raw = await fsPromises3.readFile(fullPath, "utf8");
10009
10205
  const fm = extractFrontmatterBlock(raw);
@@ -10040,7 +10236,7 @@ async function runIntakeBranch(opts) {
10040
10236
  labelFilter = "cleargate:proposal",
10041
10237
  now = () => (/* @__PURE__ */ new Date()).toISOString()
10042
10238
  } = opts;
10043
- const pendingSyncDir = path46.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10239
+ const pendingSyncDir = path47.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10044
10240
  let remoteItems = [];
10045
10241
  try {
10046
10242
  remoteItems = await mcp2.call(
@@ -10071,7 +10267,7 @@ async function runIntakeBranch(opts) {
10071
10267
  const slug2 = slugify(item.title ?? "untitled");
10072
10268
  const num2 = proposalId2.replace("PROP-", "");
10073
10269
  const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
10074
- const targetPath2 = path46.join(pendingSyncDir, filename2);
10270
+ const targetPath2 = path47.join(pendingSyncDir, filename2);
10075
10271
  createdItems.push({
10076
10272
  proposalId: proposalId2,
10077
10273
  remoteId: item.remote_id,
@@ -10084,7 +10280,7 @@ async function runIntakeBranch(opts) {
10084
10280
  const num = proposalId.replace("PROP-", "");
10085
10281
  const slug = slugify(item.title ?? "untitled");
10086
10282
  const filename = `PROPOSAL-${num}-remote-${slug}.md`;
10087
- const targetPath = path46.join(pendingSyncDir, filename);
10283
+ const targetPath = path47.join(pendingSyncDir, filename);
10088
10284
  const nowTs = now();
10089
10285
  const fm = {
10090
10286
  proposal_id: proposalId,
@@ -10176,8 +10372,8 @@ path/to/new/file.ext - {Explanation of purpose}
10176
10372
  }
10177
10373
  async function hasAnyRemoteAuthored(projectRoot) {
10178
10374
  const dirs = [
10179
- path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10180
- path46.join(projectRoot, ".cleargate", "delivery", "archive")
10375
+ path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10376
+ path47.join(projectRoot, ".cleargate", "delivery", "archive")
10181
10377
  ];
10182
10378
  for (const dir of dirs) {
10183
10379
  let entries;
@@ -10188,7 +10384,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
10188
10384
  }
10189
10385
  for (const entry of entries) {
10190
10386
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10191
- const fullPath = path46.join(dir, entry.name);
10387
+ const fullPath = path47.join(dir, entry.name);
10192
10388
  try {
10193
10389
  const raw = await fsPromises4.readFile(fullPath, "utf8");
10194
10390
  const fmEnd = raw.indexOf("\n---", 4);
@@ -10206,9 +10402,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
10206
10402
 
10207
10403
  // src/lib/active-criteria.ts
10208
10404
  init_cjs_shims();
10209
- var fs42 = __toESM(require("fs"), 1);
10405
+ var fs43 = __toESM(require("fs"), 1);
10210
10406
  var fsPromises5 = __toESM(require("fs/promises"), 1);
10211
- var path47 = __toESM(require("path"), 1);
10407
+ var path48 = __toESM(require("path"), 1);
10212
10408
  async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
10213
10409
  const active = /* @__PURE__ */ new Set();
10214
10410
  const now = Date.parse(nowFn());
@@ -10233,7 +10429,7 @@ async function resolveInSprintIds(projectRoot) {
10233
10429
  const ids = /* @__PURE__ */ new Set();
10234
10430
  try {
10235
10431
  const sprintDir = resolveActiveSprintDir(projectRoot);
10236
- const sprintId = path47.basename(sprintDir);
10432
+ const sprintId = path48.basename(sprintDir);
10237
10433
  if (sprintId === "_off-sprint") return ids;
10238
10434
  const sprintFile = await findSprintFile2(projectRoot, sprintId);
10239
10435
  if (!sprintFile) return ids;
@@ -10248,14 +10444,14 @@ async function resolveInSprintIds(projectRoot) {
10248
10444
  return ids;
10249
10445
  }
10250
10446
  async function findSprintFile2(projectRoot, sprintId) {
10251
- const pendingSync = path47.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10252
- const archive = path47.join(projectRoot, ".cleargate", "delivery", "archive");
10447
+ const pendingSync = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10448
+ const archive = path48.join(projectRoot, ".cleargate", "delivery", "archive");
10253
10449
  for (const dir of [pendingSync, archive]) {
10254
10450
  try {
10255
- const entries = fs42.readdirSync(dir, { withFileTypes: true });
10451
+ const entries = fs43.readdirSync(dir, { withFileTypes: true });
10256
10452
  for (const entry of entries) {
10257
10453
  if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
10258
- return path47.join(dir, entry.name);
10454
+ return path48.join(dir, entry.name);
10259
10455
  }
10260
10456
  }
10261
10457
  } catch {
@@ -10267,12 +10463,12 @@ async function findSprintFile2(projectRoot, sprintId) {
10267
10463
  // src/lib/comments-cache.ts
10268
10464
  init_cjs_shims();
10269
10465
  var fsPromises6 = __toESM(require("fs/promises"), 1);
10270
- var path48 = __toESM(require("path"), 1);
10466
+ var path49 = __toESM(require("path"), 1);
10271
10467
  function cacheDir(projectRoot) {
10272
- return path48.join(projectRoot, ".cleargate", ".comments-cache");
10468
+ return path49.join(projectRoot, ".cleargate", ".comments-cache");
10273
10469
  }
10274
10470
  function cachePath(projectRoot, remoteId) {
10275
- return path48.join(cacheDir(projectRoot), `${remoteId}.json`);
10471
+ return path49.join(cacheDir(projectRoot), `${remoteId}.json`);
10276
10472
  }
10277
10473
  async function writeCommentCache(projectRoot, remoteId, comments) {
10278
10474
  const dir = cacheDir(projectRoot);
@@ -10287,7 +10483,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
10287
10483
  // src/lib/wiki-comments-render.ts
10288
10484
  init_cjs_shims();
10289
10485
  var fsPromises7 = __toESM(require("fs/promises"), 1);
10290
- var path49 = __toESM(require("path"), 1);
10486
+ var path50 = __toESM(require("path"), 1);
10291
10487
  var START = "<!-- cleargate:comments:start -->";
10292
10488
  var END = "<!-- cleargate:comments:end -->";
10293
10489
  function resolveBucket(fm) {
@@ -10332,7 +10528,7 @@ async function renderCommentsSection(opts) {
10332
10528
  const bucket = resolveBucket(localItem.fm);
10333
10529
  const primaryId = getPrimaryId(localItem.fm);
10334
10530
  if (!bucket || !primaryId) return;
10335
- const wikiPath = path49.join(
10531
+ const wikiPath = path50.join(
10336
10532
  projectRoot,
10337
10533
  ".cleargate",
10338
10534
  "wiki",
@@ -10368,7 +10564,7 @@ async function renderCommentsSection(opts) {
10368
10564
  await writeAtomic4(wikiPath, updated);
10369
10565
  }
10370
10566
  async function writeAtomic4(filePath, content) {
10371
- await fsPromises7.mkdir(path49.dirname(filePath), { recursive: true });
10567
+ await fsPromises7.mkdir(path50.dirname(filePath), { recursive: true });
10372
10568
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
10373
10569
  await fsPromises7.writeFile(tmpPath, content, "utf8");
10374
10570
  await fsPromises7.rename(tmpPath, filePath);
@@ -10380,11 +10576,11 @@ async function syncCheckHandler(opts = {}) {
10380
10576
  const env = opts.env ?? process.env;
10381
10577
  const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
10382
10578
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10383
- const markerPath = path50.join(projectRoot, ".cleargate", ".sync-marker.json");
10579
+ const markerPath = path51.join(projectRoot, ".cleargate", ".sync-marker.json");
10384
10580
  const updateMarker = async (nowIso2) => {
10385
10581
  try {
10386
10582
  const content = JSON.stringify({ last_check: nowIso2 });
10387
- await fsPromises8.mkdir(path50.dirname(markerPath), { recursive: true });
10583
+ await fsPromises8.mkdir(path51.dirname(markerPath), { recursive: true });
10388
10584
  const tmpPath = `${markerPath}.tmp.${Date.now()}`;
10389
10585
  await fsPromises8.writeFile(tmpPath, content, "utf8");
10390
10586
  await fsPromises8.rename(tmpPath, markerPath);
@@ -10467,7 +10663,7 @@ async function syncHandler(opts = {}) {
10467
10663
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10468
10664
  const identity = resolveIdentity(projectRoot);
10469
10665
  const sprintRoot = resolveActiveSprintDir(projectRoot);
10470
- const sprintId = path50.basename(sprintRoot);
10666
+ const sprintId = path51.basename(sprintRoot);
10471
10667
  let mcp2;
10472
10668
  if (opts.mcp) {
10473
10669
  mcp2 = opts.mcp;
@@ -10528,7 +10724,7 @@ async function syncHandler(opts = {}) {
10528
10724
  exit(2);
10529
10725
  return;
10530
10726
  }
10531
- const wikiMetaPath = path50.join(projectRoot, ".cleargate", "wiki", "meta.json");
10727
+ const wikiMetaPath = path51.join(projectRoot, ".cleargate", "wiki", "meta.json");
10532
10728
  let lastRemoteSync = "1970-01-01T00:00:00.000Z";
10533
10729
  try {
10534
10730
  const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
@@ -10769,7 +10965,7 @@ async function syncHandler(opts = {}) {
10769
10965
  };
10770
10966
  await appendSyncLog(sprintRoot, entry);
10771
10967
  }
10772
- const conflictsFile = path50.join(projectRoot, ".cleargate", ".conflicts.json");
10968
+ const conflictsFile = path51.join(projectRoot, ".cleargate", ".conflicts.json");
10773
10969
  const conflictsContent = {
10774
10970
  generated_at: nowFn(),
10775
10971
  sprint_id: sprintId,
@@ -10777,7 +10973,7 @@ async function syncHandler(opts = {}) {
10777
10973
  };
10778
10974
  await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
10779
10975
  try {
10780
- await fsPromises8.mkdir(path50.dirname(wikiMetaPath), { recursive: true });
10976
+ await fsPromises8.mkdir(path51.dirname(wikiMetaPath), { recursive: true });
10781
10977
  let meta = {};
10782
10978
  try {
10783
10979
  const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
@@ -10818,13 +11014,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
10818
11014
  await writeAtomic5(localPath, newContent);
10819
11015
  }
10820
11016
  async function writeAtomic5(filePath, content) {
10821
- await fsPromises8.mkdir(path50.dirname(filePath), { recursive: true });
11017
+ await fsPromises8.mkdir(path51.dirname(filePath), { recursive: true });
10822
11018
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
10823
11019
  await fsPromises8.writeFile(tmpPath, content, "utf8");
10824
11020
  await fsPromises8.rename(tmpPath, filePath);
10825
11021
  }
10826
11022
  async function scanLocalItems(projectRoot) {
10827
- const pendingSync = path50.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11023
+ const pendingSync = path51.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10828
11024
  const results = [];
10829
11025
  let entries;
10830
11026
  try {
@@ -10834,7 +11030,7 @@ async function scanLocalItems(projectRoot) {
10834
11030
  }
10835
11031
  for (const entry of entries) {
10836
11032
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10837
- const fullPath = path50.join(pendingSync, entry.name);
11033
+ const fullPath = path51.join(pendingSync, entry.name);
10838
11034
  try {
10839
11035
  const raw = await fsPromises8.readFile(fullPath, "utf8");
10840
11036
  const { fm, body } = parseFrontmatter(raw);
@@ -10862,7 +11058,7 @@ init_config();
10862
11058
  // src/lib/sync/work-items.ts
10863
11059
  init_cjs_shims();
10864
11060
  var fsPromises9 = __toESM(require("fs/promises"), 1);
10865
- var path51 = __toESM(require("path"), 1);
11061
+ var path52 = __toESM(require("path"), 1);
10866
11062
  var import_node_crypto2 = require("crypto");
10867
11063
  var BATCH_SIZE = 100;
10868
11064
  var ATTRIBUTION_FIELDS = /* @__PURE__ */ new Set([
@@ -10910,8 +11106,8 @@ function getItemId2(fm) {
10910
11106
  }
10911
11107
  async function walkDeliveryDirs(projectRoot) {
10912
11108
  const dirs = [
10913
- path51.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10914
- path51.join(projectRoot, ".cleargate", "delivery", "archive")
11109
+ path52.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
11110
+ path52.join(projectRoot, ".cleargate", "delivery", "archive")
10915
11111
  ];
10916
11112
  const results = [];
10917
11113
  for (const dir of dirs) {
@@ -10923,7 +11119,7 @@ async function walkDeliveryDirs(projectRoot) {
10923
11119
  }
10924
11120
  for (const entry of entries) {
10925
11121
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10926
- const fullPath = path51.join(dir, entry.name);
11122
+ const fullPath = path52.join(dir, entry.name);
10927
11123
  try {
10928
11124
  const raw = await fsPromises9.readFile(fullPath, "utf8");
10929
11125
  const { fm, body } = parseFrontmatter(raw);
@@ -10948,7 +11144,7 @@ async function walkDeliveryDirs(projectRoot) {
10948
11144
  return results;
10949
11145
  }
10950
11146
  async function writeAtomic6(filePath, content) {
10951
- await fsPromises9.mkdir(path51.dirname(filePath), { recursive: true });
11147
+ await fsPromises9.mkdir(path52.dirname(filePath), { recursive: true });
10952
11148
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
10953
11149
  await fsPromises9.writeFile(tmpPath, content, "utf8");
10954
11150
  await fsPromises9.rename(tmpPath, filePath);
@@ -11015,9 +11211,9 @@ async function syncWorkItems(opts) {
11015
11211
 
11016
11212
  // src/lib/admin-url.ts
11017
11213
  init_cjs_shims();
11018
- var fs43 = __toESM(require("fs"), 1);
11019
- var os9 = __toESM(require("os"), 1);
11020
- var path52 = __toESM(require("path"), 1);
11214
+ var fs44 = __toESM(require("fs"), 1);
11215
+ var os10 = __toESM(require("os"), 1);
11216
+ var path53 = __toESM(require("path"), 1);
11021
11217
  var DEFAULT_BASE = "https://admin.cleargate.soula.ge/";
11022
11218
  function adminUrl(urlPath, opts) {
11023
11219
  const env = opts?.env ?? process.env;
@@ -11038,10 +11234,10 @@ function adminUrl(urlPath, opts) {
11038
11234
  return base;
11039
11235
  }
11040
11236
  function readLocalConfig() {
11041
- const home = os9.homedir();
11237
+ const home = os10.homedir();
11042
11238
  if (!home) return null;
11043
- const configPath = path52.join(home, ".cleargate", "config.json");
11044
- const raw = fs43.readFileSync(configPath, "utf8");
11239
+ const configPath = path53.join(home, ".cleargate", "config.json");
11240
+ const raw = fs44.readFileSync(configPath, "utf8");
11045
11241
  return JSON.parse(raw);
11046
11242
  }
11047
11243
 
@@ -11156,7 +11352,7 @@ async function syncWorkItemsHandler(opts = {}) {
11156
11352
  // src/commands/pull.ts
11157
11353
  init_cjs_shims();
11158
11354
  var fsPromises10 = __toESM(require("fs/promises"), 1);
11159
- var path53 = __toESM(require("path"), 1);
11355
+ var path54 = __toESM(require("path"), 1);
11160
11356
  init_acquire();
11161
11357
  init_config();
11162
11358
  async function pullHandler(idOrRemoteId, opts = {}) {
@@ -11271,7 +11467,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
11271
11467
  result: "ok"
11272
11468
  };
11273
11469
  await appendSyncLog(sprintRoot, entry);
11274
- stdout(`pull: ${remoteId} applied to ${path53.relative(projectRoot, localPath)}
11470
+ stdout(`pull: ${remoteId} applied to ${path54.relative(projectRoot, localPath)}
11275
11471
  `);
11276
11472
  if (opts.comments) {
11277
11473
  const comments = await mcp2.call(
@@ -11294,7 +11490,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11294
11490
  if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
11295
11491
  return idOrRemoteId;
11296
11492
  }
11297
- const pendingSync = path53.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11493
+ const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11298
11494
  let entries;
11299
11495
  try {
11300
11496
  entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
@@ -11304,7 +11500,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11304
11500
  for (const entry of entries) {
11305
11501
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11306
11502
  try {
11307
- const raw = await fsPromises10.readFile(path53.join(pendingSync, entry.name), "utf8");
11503
+ const raw = await fsPromises10.readFile(path54.join(pendingSync, entry.name), "utf8");
11308
11504
  const { fm } = parseFrontmatter(raw);
11309
11505
  for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
11310
11506
  if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
@@ -11317,7 +11513,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11317
11513
  return null;
11318
11514
  }
11319
11515
  async function findLocalFile(remoteId, projectRoot) {
11320
- const pendingSync = path53.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11516
+ const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11321
11517
  let entries;
11322
11518
  try {
11323
11519
  entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
@@ -11326,7 +11522,7 @@ async function findLocalFile(remoteId, projectRoot) {
11326
11522
  }
11327
11523
  for (const entry of entries) {
11328
11524
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11329
- const fullPath = path53.join(pendingSync, entry.name);
11525
+ const fullPath = path54.join(pendingSync, entry.name);
11330
11526
  try {
11331
11527
  const raw = await fsPromises10.readFile(fullPath, "utf8");
11332
11528
  const { fm } = parseFrontmatter(raw);
@@ -11337,7 +11533,7 @@ async function findLocalFile(remoteId, projectRoot) {
11337
11533
  return null;
11338
11534
  }
11339
11535
  async function writeAtomic7(filePath, content) {
11340
- await fsPromises10.mkdir(path53.dirname(filePath), { recursive: true });
11536
+ await fsPromises10.mkdir(path54.dirname(filePath), { recursive: true });
11341
11537
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11342
11538
  await fsPromises10.writeFile(tmpPath, content, "utf8");
11343
11539
  await fsPromises10.rename(tmpPath, filePath);
@@ -11353,7 +11549,7 @@ function getItemId3(fm) {
11353
11549
  // src/commands/push.ts
11354
11550
  init_cjs_shims();
11355
11551
  var fsPromises11 = __toESM(require("fs/promises"), 1);
11356
- var path54 = __toESM(require("path"), 1);
11552
+ var path55 = __toESM(require("path"), 1);
11357
11553
  init_acquire();
11358
11554
  init_config();
11359
11555
  async function pushHandler(fileOrId, opts = {}) {
@@ -11429,7 +11625,20 @@ async function pushHandler(fileOrId, opts = {}) {
11429
11625
  }
11430
11626
  async function handlePush(filePath, ctx) {
11431
11627
  const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
11432
- const resolvedPath = path54.isAbsolute(filePath) ? filePath : path54.resolve(projectRoot, filePath);
11628
+ const resolvedPath = path55.isAbsolute(filePath) ? filePath : path55.resolve(projectRoot, filePath);
11629
+ if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
11630
+ if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
11631
+ stderr(
11632
+ `Error: path not in allowlist. Only sprint report files are accepted from sprint-runs/.
11633
+ Allowed: .cleargate/sprint-runs/SPRINT-NN/REPORT.md
11634
+ .cleargate/sprint-runs/SPRINT-NN/SPRINT-NN_REPORT.md
11635
+ Got: "${resolvedPath}"
11636
+ `
11637
+ );
11638
+ exit(2);
11639
+ return;
11640
+ }
11641
+ }
11433
11642
  let rawContent;
11434
11643
  try {
11435
11644
  rawContent = await fsPromises11.readFile(resolvedPath, "utf8");
@@ -11459,7 +11668,7 @@ async function handlePush(filePath, ctx) {
11459
11668
  return;
11460
11669
  }
11461
11670
  const itemId = getItemId4(fm);
11462
- const type = getItemType2(fm);
11671
+ const type = getItemTypeWithPathOverride(resolvedPath, fm);
11463
11672
  if (!type) {
11464
11673
  stderr(`Error: cannot determine item type from frontmatter in "${resolvedPath}".
11465
11674
  `);
@@ -11472,6 +11681,9 @@ async function handlePush(filePath, ctx) {
11472
11681
  if (h1) payloadForPush["title"] = h1;
11473
11682
  }
11474
11683
  payloadForPush["body"] = body;
11684
+ if (payloadForPush["origin"] === void 0) {
11685
+ payloadForPush["origin"] = "cleargate-cli";
11686
+ }
11475
11687
  const mcp2 = await resolveMcp();
11476
11688
  let result;
11477
11689
  try {
@@ -11555,8 +11767,8 @@ async function handleRevert(idOrRemoteId, ctx) {
11555
11767
  void localPath;
11556
11768
  }
11557
11769
  async function resolveLocalItem(idOrRemoteId, projectRoot) {
11558
- const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11559
- const archive = path54.join(projectRoot, ".cleargate", "delivery", "archive");
11770
+ const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11771
+ const archive = path55.join(projectRoot, ".cleargate", "delivery", "archive");
11560
11772
  for (const dir of [pendingSync, archive]) {
11561
11773
  let entries;
11562
11774
  try {
@@ -11566,7 +11778,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
11566
11778
  }
11567
11779
  for (const entry of entries) {
11568
11780
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11569
- const fullPath = path54.join(dir, entry.name);
11781
+ const fullPath = path55.join(dir, entry.name);
11570
11782
  try {
11571
11783
  const raw = await fsPromises11.readFile(fullPath, "utf8");
11572
11784
  const { fm } = parseFrontmatter(raw);
@@ -11585,23 +11797,26 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
11585
11797
  return null;
11586
11798
  }
11587
11799
  async function writeAtomic8(filePath, content) {
11588
- await fsPromises11.mkdir(path54.dirname(filePath), { recursive: true });
11800
+ await fsPromises11.mkdir(path55.dirname(filePath), { recursive: true });
11589
11801
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11590
11802
  await fsPromises11.writeFile(tmpPath, content, "utf8");
11591
11803
  await fsPromises11.rename(tmpPath, filePath);
11592
11804
  }
11593
11805
  function getItemId4(fm) {
11594
- for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
11806
+ for (const key of ["story_id", "epic_id", "proposal_id", "sprint_id", "cr_id", "bug_id"]) {
11595
11807
  const val = fm[key];
11596
11808
  if (typeof val === "string" && val) return val;
11597
11809
  }
11598
11810
  return "unknown";
11599
11811
  }
11812
+ var SPRINT_RUNS_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]/;
11813
+ var SPRINT_REPORT_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]SPRINT-\d{2,}[\\/](REPORT|SPRINT-\d{2,}_REPORT)\.md$/;
11600
11814
  function getItemType2(fm) {
11601
11815
  const typeMap = {
11602
11816
  story_id: "story",
11603
11817
  epic_id: "epic",
11604
11818
  proposal_id: "proposal",
11819
+ sprint_id: "sprint",
11605
11820
  cr_id: "cr",
11606
11821
  bug_id: "bug"
11607
11822
  };
@@ -11610,11 +11825,15 @@ function getItemType2(fm) {
11610
11825
  }
11611
11826
  return null;
11612
11827
  }
11828
+ function getItemTypeWithPathOverride(localPath, fm) {
11829
+ if (SPRINT_REPORT_PATH_REGEX.test(localPath)) return "sprint_report";
11830
+ return getItemType2(fm);
11831
+ }
11613
11832
 
11614
11833
  // src/commands/conflicts.ts
11615
11834
  init_cjs_shims();
11616
11835
  var fsPromises12 = __toESM(require("fs/promises"), 1);
11617
- var path55 = __toESM(require("path"), 1);
11836
+ var path56 = __toESM(require("path"), 1);
11618
11837
  init_acquire();
11619
11838
  init_config();
11620
11839
  var RESOLUTION_HINTS = {
@@ -11652,7 +11871,7 @@ async function conflictsHandler(opts = {}) {
11652
11871
  }
11653
11872
  }
11654
11873
  }
11655
- const conflictsFile = path55.join(projectRoot, ".cleargate", ".conflicts.json");
11874
+ const conflictsFile = path56.join(projectRoot, ".cleargate", ".conflicts.json");
11656
11875
  let data;
11657
11876
  try {
11658
11877
  const raw = await fsPromises12.readFile(conflictsFile, "utf8");
@@ -11740,24 +11959,24 @@ function formatEntry(entry) {
11740
11959
 
11741
11960
  // src/commands/admin-login.ts
11742
11961
  init_cjs_shims();
11743
- var fs44 = __toESM(require("fs"), 1);
11744
- var path56 = __toESM(require("path"), 1);
11745
- var os10 = __toESM(require("os"), 1);
11962
+ var fs45 = __toESM(require("fs"), 1);
11963
+ var path57 = __toESM(require("path"), 1);
11964
+ var os11 = __toESM(require("os"), 1);
11746
11965
  var DEFAULT_MCP_URL = "http://localhost:3000";
11747
11966
  function resolveMcpUrl(mcpUrlFlag, env) {
11748
11967
  return (mcpUrlFlag ?? (env ?? process.env)["CLEARGATE_MCP_URL"] ?? DEFAULT_MCP_URL).replace(/\/$/, "");
11749
11968
  }
11750
11969
  function resolveAuthFilePath(opts) {
11751
11970
  if (opts.authFilePath) return opts.authFilePath;
11752
- const homedirFn = opts.homedir ?? os10.homedir;
11753
- return path56.join(homedirFn(), ".cleargate", "admin-auth.json");
11971
+ const homedirFn = opts.homedir ?? os11.homedir;
11972
+ return path57.join(homedirFn(), ".cleargate", "admin-auth.json");
11754
11973
  }
11755
11974
  function writeAdminAuth(filePath, token) {
11756
- const dir = path56.dirname(filePath);
11757
- fs44.mkdirSync(dir, { recursive: true });
11975
+ const dir = path57.dirname(filePath);
11976
+ fs45.mkdirSync(dir, { recursive: true });
11758
11977
  const payload = JSON.stringify({ version: 1, token }, null, 2);
11759
- fs44.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
11760
- fs44.chmodSync(filePath, 384);
11978
+ fs45.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
11979
+ fs45.chmodSync(filePath, 384);
11761
11980
  }
11762
11981
  async function adminLoginHandler(opts = {}) {
11763
11982
  const fetchFn = opts.fetch ?? globalThis.fetch;
@@ -11867,8 +12086,8 @@ async function adminLoginHandler(opts = {}) {
11867
12086
 
11868
12087
  // src/commands/hotfix.ts
11869
12088
  init_cjs_shims();
11870
- var fs45 = __toESM(require("fs"), 1);
11871
- var path57 = __toESM(require("path"), 1);
12089
+ var fs46 = __toESM(require("fs"), 1);
12090
+ var path58 = __toESM(require("path"), 1);
11872
12091
  function defaultExit4(code) {
11873
12092
  return process.exit(code);
11874
12093
  }
@@ -11878,7 +12097,7 @@ function maxHotfixId(pendingDir) {
11878
12097
  let max = 0;
11879
12098
  let entries;
11880
12099
  try {
11881
- entries = fs45.readdirSync(pendingDir);
12100
+ entries = fs46.readdirSync(pendingDir);
11882
12101
  } catch {
11883
12102
  return 0;
11884
12103
  }
@@ -11892,13 +12111,13 @@ function maxHotfixId(pendingDir) {
11892
12111
  return max;
11893
12112
  }
11894
12113
  function countActiveHotfixes(repoRoot) {
11895
- const pendingDir = path57.join(repoRoot, ".cleargate", "delivery", "pending-sync");
11896
- const archiveDir = path57.join(repoRoot, ".cleargate", "delivery", "archive");
12114
+ const pendingDir = path58.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12115
+ const archiveDir = path58.join(repoRoot, ".cleargate", "delivery", "archive");
11897
12116
  const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
11898
12117
  let count = 0;
11899
12118
  let pendingEntries = [];
11900
12119
  try {
11901
- pendingEntries = fs45.readdirSync(pendingDir);
12120
+ pendingEntries = fs46.readdirSync(pendingDir);
11902
12121
  } catch {
11903
12122
  }
11904
12123
  for (const entry of pendingEntries) {
@@ -11906,13 +12125,13 @@ function countActiveHotfixes(repoRoot) {
11906
12125
  }
11907
12126
  let archiveEntries = [];
11908
12127
  try {
11909
- archiveEntries = fs45.readdirSync(archiveDir);
12128
+ archiveEntries = fs46.readdirSync(archiveDir);
11910
12129
  } catch {
11911
12130
  }
11912
12131
  for (const entry of archiveEntries) {
11913
12132
  if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
11914
12133
  try {
11915
- const stat = fs45.statSync(path57.join(archiveDir, entry));
12134
+ const stat = fs46.statSync(path58.join(archiveDir, entry));
11916
12135
  if (stat.mtimeMs >= sevenDaysAgo) count++;
11917
12136
  } catch {
11918
12137
  }
@@ -11921,7 +12140,7 @@ function countActiveHotfixes(repoRoot) {
11921
12140
  return count;
11922
12141
  }
11923
12142
  function resolveTemplatePath(repoRoot) {
11924
- return path57.join(repoRoot, ".cleargate", "templates", "hotfix.md");
12143
+ return path58.join(repoRoot, ".cleargate", "templates", "hotfix.md");
11925
12144
  }
11926
12145
  function hotfixNewHandler(opts, cli) {
11927
12146
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -11940,14 +12159,14 @@ function hotfixNewHandler(opts, cli) {
11940
12159
  );
11941
12160
  return exitFn(1);
11942
12161
  }
11943
- const pendingDir = path57.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12162
+ const pendingDir = path58.join(repoRoot, ".cleargate", "delivery", "pending-sync");
11944
12163
  const maxId = maxHotfixId(pendingDir);
11945
12164
  const nextId = maxId + 1;
11946
12165
  const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
11947
12166
  const templatePath = resolveTemplatePath(repoRoot);
11948
12167
  let templateContent;
11949
12168
  try {
11950
- templateContent = fs45.readFileSync(templatePath, "utf8");
12169
+ templateContent = fs46.readFileSync(templatePath, "utf8");
11951
12170
  } catch {
11952
12171
  stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
11953
12172
  return exitFn(2);
@@ -11955,10 +12174,10 @@ function hotfixNewHandler(opts, cli) {
11955
12174
  const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
11956
12175
  const fileSlug = opts.slug.replace(/-/g, "_");
11957
12176
  const fileName = `${idStr}_${fileSlug}.md`;
11958
- const outPath = path57.join(pendingDir, fileName);
12177
+ const outPath = path58.join(pendingDir, fileName);
11959
12178
  try {
11960
- fs45.mkdirSync(pendingDir, { recursive: true });
11961
- fs45.writeFileSync(outPath, content, "utf8");
12179
+ fs46.mkdirSync(pendingDir, { recursive: true });
12180
+ fs46.writeFileSync(outPath, content, "utf8");
11962
12181
  } catch (err) {
11963
12182
  const msg = err instanceof Error ? err.message : String(err);
11964
12183
  stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
@@ -12046,8 +12265,26 @@ var AuthFetcher = class {
12046
12265
  }
12047
12266
  };
12048
12267
 
12268
+ // src/auth/service-token-fetcher.ts
12269
+ init_cjs_shims();
12270
+ var ServiceTokenFetcher = class {
12271
+ constructor(token) {
12272
+ this.token = token;
12273
+ }
12274
+ token;
12275
+ async getAccessToken() {
12276
+ return this.token;
12277
+ }
12278
+ };
12279
+
12049
12280
  // src/commands/mcp-serve.ts
12050
12281
  var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
12282
+ var ServiceToken401Error = class extends Error {
12283
+ constructor() {
12284
+ super("service-token-401");
12285
+ this.name = "ServiceToken401Error";
12286
+ }
12287
+ };
12051
12288
  async function mcpServeHandler(opts) {
12052
12289
  const fetchFn = opts.fetch ?? globalThis.fetch;
12053
12290
  const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
@@ -12057,32 +12294,44 @@ async function mcpServeHandler(opts) {
12057
12294
  flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
12058
12295
  });
12059
12296
  const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
12060
- const store = await (opts.createStore ?? createTokenStore)({
12061
- ...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
12062
- ...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
12063
- });
12064
- const fetcher = new AuthFetcher({
12065
- baseUrl,
12066
- loadRefresh: () => store.load(opts.profile),
12067
- saveRefresh: (t) => store.save(opts.profile, t),
12068
- ...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
12069
- ...opts.now !== void 0 ? { now: opts.now } : {}
12070
- });
12071
- try {
12072
- await fetcher.getAccessToken();
12073
- } catch (err) {
12074
- if (err instanceof RefreshError) {
12075
- stderr(
12076
- `cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
12297
+ const serviceToken = process.env["CLEARGATE_SERVICE_TOKEN"] ?? "";
12298
+ let fetcher;
12299
+ let isServiceTokenMode;
12300
+ if (serviceToken.length > 0) {
12301
+ isServiceTokenMode = true;
12302
+ fetcher = new ServiceTokenFetcher(serviceToken);
12303
+ stderr("cleargate mcp serve: auth mode = service-token\n");
12304
+ } else {
12305
+ isServiceTokenMode = false;
12306
+ const store = await (opts.createStore ?? createTokenStore)({
12307
+ ...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
12308
+ ...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
12309
+ });
12310
+ const authFetcher = new AuthFetcher({
12311
+ baseUrl,
12312
+ loadRefresh: () => store.load(opts.profile),
12313
+ saveRefresh: (t) => store.save(opts.profile, t),
12314
+ ...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
12315
+ ...opts.now !== void 0 ? { now: opts.now } : {}
12316
+ });
12317
+ fetcher = authFetcher;
12318
+ stderr("cleargate mcp serve: auth mode = keychain-refresh\n");
12319
+ try {
12320
+ await authFetcher.getAccessToken();
12321
+ } catch (err) {
12322
+ if (err instanceof RefreshError) {
12323
+ stderr(
12324
+ `cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
12077
12325
  `
12078
- );
12079
- } else {
12080
- stderr(
12081
- `cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
12326
+ );
12327
+ } else {
12328
+ stderr(
12329
+ `cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
12082
12330
  `
12083
- );
12331
+ );
12332
+ }
12333
+ return exit(1);
12084
12334
  }
12085
- return exit(1);
12086
12335
  }
12087
12336
  const inputStream = opts.stdin ?? process.stdin;
12088
12337
  const rl = readline5.createInterface({
@@ -12093,8 +12342,23 @@ async function mcpServeHandler(opts) {
12093
12342
  for await (const line of rl) {
12094
12343
  if (!line.trim()) continue;
12095
12344
  try {
12096
- await proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr);
12345
+ await proxyOne(
12346
+ line,
12347
+ baseUrl,
12348
+ fetcher,
12349
+ isServiceTokenMode,
12350
+ fetchFn,
12351
+ stdout,
12352
+ stderr
12353
+ );
12097
12354
  } catch (err) {
12355
+ if (err instanceof ServiceToken401Error) {
12356
+ stderr(
12357
+ `cleargate mcp serve: CLEARGATE_SERVICE_TOKEN rejected by /mcp (401). Issue a new token in the admin console: Tokens \u2192 Issue \u2192 copy snippet.
12358
+ `
12359
+ );
12360
+ return exit(1);
12361
+ }
12098
12362
  const errMsg = err instanceof Error ? err.message : String(err);
12099
12363
  stderr(`cleargate mcp serve: proxy error: ${errMsg}
12100
12364
  `);
@@ -12111,7 +12375,7 @@ async function mcpServeHandler(opts) {
12111
12375
  }
12112
12376
  }
12113
12377
  }
12114
- async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
12378
+ async function proxyOne(line, baseUrl, fetcher, isServiceTokenMode, fetchFn, stdout, stderr) {
12115
12379
  let parsed;
12116
12380
  try {
12117
12381
  parsed = JSON.parse(line);
@@ -12124,6 +12388,9 @@ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
12124
12388
  let access = await fetcher.getAccessToken();
12125
12389
  let res = await postFrame(baseUrl, line, access, fetchFn);
12126
12390
  if (res.status === 401) {
12391
+ if (isServiceTokenMode) {
12392
+ throw new ServiceToken401Error();
12393
+ }
12127
12394
  fetcher.invalidate();
12128
12395
  access = await fetcher.getAccessToken();
12129
12396
  res = await postFrame(baseUrl, line, access, fetchFn);