cleargate 0.11.5 → 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/cli.cjs +245 -72
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +244 -72
- 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/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/cli.cjs
CHANGED
|
@@ -783,7 +783,7 @@ var import_commander = require("commander");
|
|
|
783
783
|
// package.json
|
|
784
784
|
var package_default = {
|
|
785
785
|
name: "cleargate",
|
|
786
|
-
version: "0.
|
|
786
|
+
version: "0.12.0",
|
|
787
787
|
private: false,
|
|
788
788
|
type: "module",
|
|
789
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.",
|
|
@@ -2304,6 +2304,24 @@ var PREFIX_MAP = [
|
|
|
2304
2304
|
{ prefix: "BUG-", type: "bug", bucket: "bugs" },
|
|
2305
2305
|
{ prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
|
|
2306
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
|
+
}
|
|
2307
2325
|
function deriveBucket(filename) {
|
|
2308
2326
|
const base = filename.includes("/") ? filename.split("/").pop() : filename;
|
|
2309
2327
|
const stem = base.endsWith(".md") ? base.slice(0, -3) : base;
|
|
@@ -2427,6 +2445,12 @@ function serializePage(page, body) {
|
|
|
2427
2445
|
if (page.sprint_cleargate_id !== void 0) {
|
|
2428
2446
|
lines.push(`sprint_cleargate_id: "${page.sprint_cleargate_id}"`);
|
|
2429
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
|
+
}
|
|
2430
2454
|
lines.push("---");
|
|
2431
2455
|
const fm = lines.join("\n");
|
|
2432
2456
|
return `${fm}
|
|
@@ -2449,7 +2473,9 @@ function parsePage(raw) {
|
|
|
2449
2473
|
const last_contradict_sha = fm["last_contradict_sha"] !== void 0 ? String(fm["last_contradict_sha"]) : void 0;
|
|
2450
2474
|
const parent_cleargate_id = fm["parent_cleargate_id"] !== void 0 ? String(fm["parent_cleargate_id"]) : void 0;
|
|
2451
2475
|
const sprint_cleargate_id = fm["sprint_cleargate_id"] !== void 0 ? String(fm["sprint_cleargate_id"]) : void 0;
|
|
2452
|
-
|
|
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 };
|
|
2453
2479
|
}
|
|
2454
2480
|
function parseFmRaw(raw) {
|
|
2455
2481
|
const lines = raw.split("\n");
|
|
@@ -3981,28 +4007,33 @@ async function wikiIngestHandler(opts) {
|
|
|
3981
4007
|
const rawPath = opts.rawPath;
|
|
3982
4008
|
const absRawPath = path20.isAbsolute(rawPath) ? rawPath : path20.resolve(cwd, rawPath);
|
|
3983
4009
|
const relRawPath = path20.relative(cwd, absRawPath).replace(/\\/g, "/");
|
|
3984
|
-
const
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
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/
|
|
3990
4017
|
`);
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
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)
|
|
3998
4024
|
`);
|
|
3999
|
-
|
|
4000
|
-
|
|
4025
|
+
exit(0);
|
|
4026
|
+
return;
|
|
4027
|
+
}
|
|
4001
4028
|
}
|
|
4002
4029
|
const filename = path20.basename(absRawPath);
|
|
4003
4030
|
let bucketInfo;
|
|
4004
4031
|
try {
|
|
4005
|
-
|
|
4032
|
+
if (isSprintReport) {
|
|
4033
|
+
bucketInfo = deriveBucketFromReportPath(relRawPath);
|
|
4034
|
+
} else {
|
|
4035
|
+
bucketInfo = deriveBucket(filename);
|
|
4036
|
+
}
|
|
4006
4037
|
} catch (e) {
|
|
4007
4038
|
stderr(`wiki ingest: cannot determine bucket for ${rawPath}: ${e.message}
|
|
4008
4039
|
`);
|
|
@@ -4045,16 +4076,26 @@ async function wikiIngestHandler(opts) {
|
|
|
4045
4076
|
}
|
|
4046
4077
|
const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
|
|
4047
4078
|
const pageExists = fs19.existsSync(pagePath);
|
|
4048
|
-
|
|
4049
|
-
|
|
4079
|
+
let existingPage;
|
|
4080
|
+
if (pageExists) {
|
|
4050
4081
|
try {
|
|
4051
4082
|
const existingPageContent = fs19.readFileSync(pagePath, "utf8");
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
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) {
|
|
4056
4092
|
isNoOp = true;
|
|
4057
4093
|
}
|
|
4094
|
+
} else {
|
|
4095
|
+
if (existingPage.last_ingest_commit === currentSha) {
|
|
4096
|
+
const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
|
|
4097
|
+
if (contentUnchanged) isNoOp = true;
|
|
4098
|
+
}
|
|
4058
4099
|
}
|
|
4059
4100
|
} catch {
|
|
4060
4101
|
}
|
|
@@ -4066,36 +4107,53 @@ async function wikiIngestHandler(opts) {
|
|
|
4066
4107
|
}
|
|
4067
4108
|
}
|
|
4068
4109
|
const action = pageExists ? "update" : "create";
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
const existingPage = parsePage(existingPageContent);
|
|
4074
|
-
existingLastContradictSha = existingPage.last_contradict_sha;
|
|
4075
|
-
} catch {
|
|
4076
|
-
}
|
|
4077
|
-
}
|
|
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;
|
|
4078
4114
|
const parent = buildParentRef2(fm);
|
|
4079
4115
|
const children = buildChildrenRefs2(fm);
|
|
4080
|
-
const timestamp = now();
|
|
4081
4116
|
const wikiPage = {
|
|
4082
4117
|
type,
|
|
4083
4118
|
id,
|
|
4084
|
-
parent,
|
|
4085
|
-
children,
|
|
4086
|
-
status: String(fm["status"] ?? ""),
|
|
4087
|
-
remote_id: String(fm["remote_id"] ?? ""),
|
|
4088
|
-
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,
|
|
4089
4125
|
last_ingest: timestamp,
|
|
4090
|
-
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,
|
|
4091
4128
|
repo,
|
|
4092
4129
|
// Carry forward last_contradict_sha so Phase 4 idempotency survives re-ingest
|
|
4093
4130
|
...existingLastContradictSha !== void 0 ? { last_contradict_sha: existingLastContradictSha } : {},
|
|
4094
|
-
// Hierarchy keys (§11.7): read from raw fm
|
|
4095
|
-
...typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
|
|
4096
|
-
...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
|
|
4097
4137
|
};
|
|
4098
|
-
|
|
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 });
|
|
4099
4157
|
const pageContent = serializePage(wikiPage, pageBody);
|
|
4100
4158
|
fs19.mkdirSync(pageDir, { recursive: true });
|
|
4101
4159
|
fs19.writeFileSync(pagePath, pageContent, "utf8");
|
|
@@ -4173,7 +4231,37 @@ function buildChildrenRefs2(fm) {
|
|
|
4173
4231
|
return `[[${s}]]`;
|
|
4174
4232
|
});
|
|
4175
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
|
+
}
|
|
4176
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) {
|
|
4177
4265
|
const title = String(item.fm["title"] ?? item.id);
|
|
4178
4266
|
const summary = String(
|
|
4179
4267
|
item.fm["description"] ?? item.body.split("\n")[0] ?? "No summary available."
|
|
@@ -4197,6 +4285,16 @@ function buildPageBody2(item) {
|
|
|
4197
4285
|
""
|
|
4198
4286
|
].join("\n");
|
|
4199
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
|
+
}
|
|
4200
4298
|
function appendLogEntry(wikiRoot, entry) {
|
|
4201
4299
|
const logPath = path20.join(wikiRoot, "log.md");
|
|
4202
4300
|
const logEntry = [
|
|
@@ -6677,7 +6775,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
6677
6775
|
throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
|
|
6678
6776
|
}
|
|
6679
6777
|
const existing = coerceCachedGate(fm["cached_gate_result"]);
|
|
6680
|
-
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)) {
|
|
6681
6779
|
return;
|
|
6682
6780
|
}
|
|
6683
6781
|
const newFm = {};
|
|
@@ -8203,6 +8301,10 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
|
|
|
8203
8301
|
if (!absPath) {
|
|
8204
8302
|
continue;
|
|
8205
8303
|
}
|
|
8304
|
+
if (absPath.includes(`${path36.sep}archive${path36.sep}`)) {
|
|
8305
|
+
result.skipped.push(id);
|
|
8306
|
+
continue;
|
|
8307
|
+
}
|
|
8206
8308
|
let status = "";
|
|
8207
8309
|
try {
|
|
8208
8310
|
const raw = fs34.readFileSync(absPath, "utf8");
|
|
@@ -11524,6 +11626,19 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
11524
11626
|
async function handlePush(filePath, ctx) {
|
|
11525
11627
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
11526
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
|
+
}
|
|
11527
11642
|
let rawContent;
|
|
11528
11643
|
try {
|
|
11529
11644
|
rawContent = await fsPromises11.readFile(resolvedPath, "utf8");
|
|
@@ -11553,7 +11668,7 @@ async function handlePush(filePath, ctx) {
|
|
|
11553
11668
|
return;
|
|
11554
11669
|
}
|
|
11555
11670
|
const itemId = getItemId4(fm);
|
|
11556
|
-
const type =
|
|
11671
|
+
const type = getItemTypeWithPathOverride(resolvedPath, fm);
|
|
11557
11672
|
if (!type) {
|
|
11558
11673
|
stderr(`Error: cannot determine item type from frontmatter in "${resolvedPath}".
|
|
11559
11674
|
`);
|
|
@@ -11566,6 +11681,9 @@ async function handlePush(filePath, ctx) {
|
|
|
11566
11681
|
if (h1) payloadForPush["title"] = h1;
|
|
11567
11682
|
}
|
|
11568
11683
|
payloadForPush["body"] = body;
|
|
11684
|
+
if (payloadForPush["origin"] === void 0) {
|
|
11685
|
+
payloadForPush["origin"] = "cleargate-cli";
|
|
11686
|
+
}
|
|
11569
11687
|
const mcp2 = await resolveMcp();
|
|
11570
11688
|
let result;
|
|
11571
11689
|
try {
|
|
@@ -11685,17 +11803,20 @@ async function writeAtomic8(filePath, content) {
|
|
|
11685
11803
|
await fsPromises11.rename(tmpPath, filePath);
|
|
11686
11804
|
}
|
|
11687
11805
|
function getItemId4(fm) {
|
|
11688
|
-
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"]) {
|
|
11689
11807
|
const val = fm[key];
|
|
11690
11808
|
if (typeof val === "string" && val) return val;
|
|
11691
11809
|
}
|
|
11692
11810
|
return "unknown";
|
|
11693
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$/;
|
|
11694
11814
|
function getItemType2(fm) {
|
|
11695
11815
|
const typeMap = {
|
|
11696
11816
|
story_id: "story",
|
|
11697
11817
|
epic_id: "epic",
|
|
11698
11818
|
proposal_id: "proposal",
|
|
11819
|
+
sprint_id: "sprint",
|
|
11699
11820
|
cr_id: "cr",
|
|
11700
11821
|
bug_id: "bug"
|
|
11701
11822
|
};
|
|
@@ -11704,6 +11825,10 @@ function getItemType2(fm) {
|
|
|
11704
11825
|
}
|
|
11705
11826
|
return null;
|
|
11706
11827
|
}
|
|
11828
|
+
function getItemTypeWithPathOverride(localPath, fm) {
|
|
11829
|
+
if (SPRINT_REPORT_PATH_REGEX.test(localPath)) return "sprint_report";
|
|
11830
|
+
return getItemType2(fm);
|
|
11831
|
+
}
|
|
11707
11832
|
|
|
11708
11833
|
// src/commands/conflicts.ts
|
|
11709
11834
|
init_cjs_shims();
|
|
@@ -12140,8 +12265,26 @@ var AuthFetcher = class {
|
|
|
12140
12265
|
}
|
|
12141
12266
|
};
|
|
12142
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
|
+
|
|
12143
12280
|
// src/commands/mcp-serve.ts
|
|
12144
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
|
+
};
|
|
12145
12288
|
async function mcpServeHandler(opts) {
|
|
12146
12289
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
12147
12290
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
@@ -12151,32 +12294,44 @@ async function mcpServeHandler(opts) {
|
|
|
12151
12294
|
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
|
|
12152
12295
|
});
|
|
12153
12296
|
const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
|
|
12154
|
-
const
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
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.
|
|
12171
12325
|
`
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12326
|
+
);
|
|
12327
|
+
} else {
|
|
12328
|
+
stderr(
|
|
12329
|
+
`cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
|
|
12176
12330
|
`
|
|
12177
|
-
|
|
12331
|
+
);
|
|
12332
|
+
}
|
|
12333
|
+
return exit(1);
|
|
12178
12334
|
}
|
|
12179
|
-
return exit(1);
|
|
12180
12335
|
}
|
|
12181
12336
|
const inputStream = opts.stdin ?? process.stdin;
|
|
12182
12337
|
const rl = readline5.createInterface({
|
|
@@ -12187,8 +12342,23 @@ async function mcpServeHandler(opts) {
|
|
|
12187
12342
|
for await (const line of rl) {
|
|
12188
12343
|
if (!line.trim()) continue;
|
|
12189
12344
|
try {
|
|
12190
|
-
await proxyOne(
|
|
12345
|
+
await proxyOne(
|
|
12346
|
+
line,
|
|
12347
|
+
baseUrl,
|
|
12348
|
+
fetcher,
|
|
12349
|
+
isServiceTokenMode,
|
|
12350
|
+
fetchFn,
|
|
12351
|
+
stdout,
|
|
12352
|
+
stderr
|
|
12353
|
+
);
|
|
12191
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
|
+
}
|
|
12192
12362
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12193
12363
|
stderr(`cleargate mcp serve: proxy error: ${errMsg}
|
|
12194
12364
|
`);
|
|
@@ -12205,7 +12375,7 @@ async function mcpServeHandler(opts) {
|
|
|
12205
12375
|
}
|
|
12206
12376
|
}
|
|
12207
12377
|
}
|
|
12208
|
-
async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
12378
|
+
async function proxyOne(line, baseUrl, fetcher, isServiceTokenMode, fetchFn, stdout, stderr) {
|
|
12209
12379
|
let parsed;
|
|
12210
12380
|
try {
|
|
12211
12381
|
parsed = JSON.parse(line);
|
|
@@ -12218,6 +12388,9 @@ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
|
12218
12388
|
let access = await fetcher.getAccessToken();
|
|
12219
12389
|
let res = await postFrame(baseUrl, line, access, fetchFn);
|
|
12220
12390
|
if (res.status === 401) {
|
|
12391
|
+
if (isServiceTokenMode) {
|
|
12392
|
+
throw new ServiceToken401Error();
|
|
12393
|
+
}
|
|
12221
12394
|
fetcher.invalidate();
|
|
12222
12395
|
access = await fetcher.getAccessToken();
|
|
12223
12396
|
res = await postFrame(baseUrl, line, access, fetchFn);
|