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/MANIFEST.json +5 -5
- package/dist/admin-api/index.cjs +3 -1
- package/dist/admin-api/index.cjs.map +1 -1
- package/dist/admin-api/index.d.cts +1 -0
- package/dist/admin-api/index.d.ts +1 -0
- package/dist/admin-api/index.js +3 -1
- package/dist/admin-api/index.js.map +1 -1
- package/dist/{chunk-Q3BTSXCK.js → chunk-WFNLCTY5.js} +90 -2
- package/dist/chunk-WFNLCTY5.js.map +1 -0
- package/dist/cli.cjs +428 -161
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +256 -76
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +6 -4
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +138 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +89 -0
- package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +5 -5
- package/dist/{whoami-W4U6DPVG.js → whoami-GQTFZHFQ.js} +2 -2
- package/package.json +1 -1
- package/templates/cleargate-planning/.claude/agents/architect.md +6 -4
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +138 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +89 -0
- package/templates/cleargate-planning/CLAUDE.md +2 -0
- package/templates/cleargate-planning/MANIFEST.json +5 -5
- package/dist/chunk-Q3BTSXCK.js.map +0 -1
- /package/dist/{whoami-W4U6DPVG.js.map → whoami-GQTFZHFQ.js.map} +0 -0
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
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
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
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
|
-
|
|
3906
|
-
|
|
4025
|
+
exit(0);
|
|
4026
|
+
return;
|
|
4027
|
+
}
|
|
3907
4028
|
}
|
|
3908
4029
|
const filename = path20.basename(absRawPath);
|
|
3909
4030
|
let bucketInfo;
|
|
3910
4031
|
try {
|
|
3911
|
-
|
|
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
|
-
|
|
3955
|
-
|
|
4079
|
+
let existingPage;
|
|
4080
|
+
if (pageExists) {
|
|
3956
4081
|
try {
|
|
3957
4082
|
const existingPageContent = fs19.readFileSync(pagePath, "utf8");
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
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
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
9962
|
-
|
|
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 =
|
|
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
|
-
|
|
9993
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
10180
|
-
|
|
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 =
|
|
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
|
|
10405
|
+
var fs43 = __toESM(require("fs"), 1);
|
|
10210
10406
|
var fsPromises5 = __toESM(require("fs/promises"), 1);
|
|
10211
|
-
var
|
|
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 =
|
|
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 =
|
|
10252
|
-
const 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 =
|
|
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
|
|
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
|
|
10466
|
+
var path49 = __toESM(require("path"), 1);
|
|
10271
10467
|
function cacheDir(projectRoot) {
|
|
10272
|
-
return
|
|
10468
|
+
return path49.join(projectRoot, ".cleargate", ".comments-cache");
|
|
10273
10469
|
}
|
|
10274
10470
|
function cachePath(projectRoot, remoteId) {
|
|
10275
|
-
return
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
10914
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
11019
|
-
var
|
|
11020
|
-
var
|
|
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 =
|
|
11237
|
+
const home = os10.homedir();
|
|
11042
11238
|
if (!home) return null;
|
|
11043
|
-
const configPath =
|
|
11044
|
-
const raw =
|
|
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
|
|
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 ${
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11559
|
-
const 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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
11744
|
-
var
|
|
11745
|
-
var
|
|
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 ??
|
|
11753
|
-
return
|
|
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 =
|
|
11757
|
-
|
|
11975
|
+
const dir = path57.dirname(filePath);
|
|
11976
|
+
fs45.mkdirSync(dir, { recursive: true });
|
|
11758
11977
|
const payload = JSON.stringify({ version: 1, token }, null, 2);
|
|
11759
|
-
|
|
11760
|
-
|
|
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
|
|
11871
|
-
var
|
|
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 =
|
|
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 =
|
|
11896
|
-
const archiveDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
12177
|
+
const outPath = path58.join(pendingDir, fileName);
|
|
11959
12178
|
try {
|
|
11960
|
-
|
|
11961
|
-
|
|
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
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
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
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
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(
|
|
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);
|