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.js
CHANGED
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
AcquireError,
|
|
10
10
|
acquireAccessToken,
|
|
11
11
|
getMembershipState,
|
|
12
|
-
loadConfig
|
|
13
|
-
|
|
12
|
+
loadConfig,
|
|
13
|
+
saveConfig
|
|
14
|
+
} from "./chunk-WFNLCTY5.js";
|
|
14
15
|
import {
|
|
15
16
|
createTokenStore
|
|
16
17
|
} from "./chunk-4V4QABOJ.js";
|
|
@@ -21,7 +22,7 @@ import { Command } from "commander";
|
|
|
21
22
|
// package.json
|
|
22
23
|
var package_default = {
|
|
23
24
|
name: "cleargate",
|
|
24
|
-
version: "0.
|
|
25
|
+
version: "0.12.0",
|
|
25
26
|
private: false,
|
|
26
27
|
type: "module",
|
|
27
28
|
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.",
|
|
@@ -633,7 +634,8 @@ async function joinHandler(opts) {
|
|
|
633
634
|
if (UUID_V4_RE.test(opts.inviteUrl)) {
|
|
634
635
|
token = opts.inviteUrl;
|
|
635
636
|
const cfg = loadConfig({
|
|
636
|
-
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
|
|
637
|
+
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag },
|
|
638
|
+
...opts.configPath !== void 0 ? { configPath: opts.configPath } : {}
|
|
637
639
|
});
|
|
638
640
|
if (!cfg.mcpUrl) {
|
|
639
641
|
stderr(
|
|
@@ -970,9 +972,15 @@ async function joinHandler(opts) {
|
|
|
970
972
|
try {
|
|
971
973
|
const store = await (opts.createStore ?? createTokenStore)();
|
|
972
974
|
await store.save(opts.profile, refreshToken);
|
|
975
|
+
saveConfig(
|
|
976
|
+
{ mcpUrl: baseUrl },
|
|
977
|
+
opts.configPath !== void 0 ? { configPath: opts.configPath } : {}
|
|
978
|
+
);
|
|
973
979
|
stdout(`joined project '${projectName}' as '${hostname3()}'
|
|
974
980
|
`);
|
|
975
981
|
stdout(`refresh token saved to ${store.backend}.
|
|
982
|
+
`);
|
|
983
|
+
stdout(`mcp_url ${baseUrl} saved to ~/.cleargate/config.json.
|
|
976
984
|
`);
|
|
977
985
|
} catch (err) {
|
|
978
986
|
stderr(
|
|
@@ -1477,6 +1485,24 @@ var PREFIX_MAP = [
|
|
|
1477
1485
|
{ prefix: "BUG-", type: "bug", bucket: "bugs" },
|
|
1478
1486
|
{ prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
|
|
1479
1487
|
];
|
|
1488
|
+
var SPRINT_REPORT_FILENAMES = ["REPORT.md"];
|
|
1489
|
+
var SPRINT_REPORT_CANONICAL_RE = /^SPRINT-\d{2,}_REPORT\.md$/;
|
|
1490
|
+
function isSprintReportPath(relPath) {
|
|
1491
|
+
const normalised = relPath.replace(/\\/g, "/");
|
|
1492
|
+
const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\/([^/]+)$/.exec(normalised);
|
|
1493
|
+
if (!match) return false;
|
|
1494
|
+
const filename = match[2];
|
|
1495
|
+
return SPRINT_REPORT_FILENAMES.includes(filename) || SPRINT_REPORT_CANONICAL_RE.test(filename);
|
|
1496
|
+
}
|
|
1497
|
+
function deriveBucketFromReportPath(relPath) {
|
|
1498
|
+
const normalised = relPath.replace(/\\/g, "/");
|
|
1499
|
+
const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\//.exec(normalised);
|
|
1500
|
+
if (!match) {
|
|
1501
|
+
throw new Error(`deriveBucketFromReportPath: cannot extract SPRINT-NN from: ${relPath}`);
|
|
1502
|
+
}
|
|
1503
|
+
const id = match[1];
|
|
1504
|
+
return { type: "sprint", id, bucket: "sprints" };
|
|
1505
|
+
}
|
|
1480
1506
|
function deriveBucket(filename) {
|
|
1481
1507
|
const base = filename.includes("/") ? filename.split("/").pop() : filename;
|
|
1482
1508
|
const stem = base.endsWith(".md") ? base.slice(0, -3) : base;
|
|
@@ -1597,6 +1623,12 @@ function serializePage(page, body) {
|
|
|
1597
1623
|
if (page.sprint_cleargate_id !== void 0) {
|
|
1598
1624
|
lines.push(`sprint_cleargate_id: "${page.sprint_cleargate_id}"`);
|
|
1599
1625
|
}
|
|
1626
|
+
if (page.report_raw_path !== void 0) {
|
|
1627
|
+
lines.push(`report_raw_path: "${page.report_raw_path}"`);
|
|
1628
|
+
}
|
|
1629
|
+
if (page.last_report_ingest_commit !== void 0) {
|
|
1630
|
+
lines.push(`last_report_ingest_commit: "${page.last_report_ingest_commit}"`);
|
|
1631
|
+
}
|
|
1600
1632
|
lines.push("---");
|
|
1601
1633
|
const fm = lines.join("\n");
|
|
1602
1634
|
return `${fm}
|
|
@@ -1619,7 +1651,9 @@ function parsePage(raw) {
|
|
|
1619
1651
|
const last_contradict_sha = fm["last_contradict_sha"] !== void 0 ? String(fm["last_contradict_sha"]) : void 0;
|
|
1620
1652
|
const parent_cleargate_id = fm["parent_cleargate_id"] !== void 0 ? String(fm["parent_cleargate_id"]) : void 0;
|
|
1621
1653
|
const sprint_cleargate_id = fm["sprint_cleargate_id"] !== void 0 ? String(fm["sprint_cleargate_id"]) : void 0;
|
|
1622
|
-
|
|
1654
|
+
const report_raw_path = fm["report_raw_path"] !== void 0 ? String(fm["report_raw_path"]) : void 0;
|
|
1655
|
+
const last_report_ingest_commit = fm["last_report_ingest_commit"] !== void 0 ? String(fm["last_report_ingest_commit"]) : void 0;
|
|
1656
|
+
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 };
|
|
1623
1657
|
}
|
|
1624
1658
|
function parseFmRaw(raw) {
|
|
1625
1659
|
const lines = raw.split("\n");
|
|
@@ -3138,28 +3172,33 @@ async function wikiIngestHandler(opts) {
|
|
|
3138
3172
|
const rawPath = opts.rawPath;
|
|
3139
3173
|
const absRawPath = path17.isAbsolute(rawPath) ? rawPath : path17.resolve(cwd, rawPath);
|
|
3140
3174
|
const relRawPath = path17.relative(cwd, absRawPath).replace(/\\/g, "/");
|
|
3141
|
-
const
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3175
|
+
const isSprintReport = isSprintReportPath(relRawPath);
|
|
3176
|
+
if (!isSprintReport) {
|
|
3177
|
+
const deliveryRoot = path17.join(cwd, ".cleargate", "delivery");
|
|
3178
|
+
const absDeliveryRoot = deliveryRoot;
|
|
3179
|
+
const relToDelivery = path17.relative(absDeliveryRoot, absRawPath);
|
|
3180
|
+
if (relToDelivery.startsWith("..") || path17.isAbsolute(relToDelivery)) {
|
|
3181
|
+
stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
|
|
3147
3182
|
`);
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
stdout(`wiki ingest: ${rawPath} excluded (skip)
|
|
3183
|
+
exit(2);
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
const isExcluded = EXCLUDED_SUFFIXES2.some((excl) => relRawPath.startsWith(excl));
|
|
3187
|
+
if (isExcluded) {
|
|
3188
|
+
stdout(`wiki ingest: ${rawPath} excluded (skip)
|
|
3155
3189
|
`);
|
|
3156
|
-
|
|
3157
|
-
|
|
3190
|
+
exit(0);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3158
3193
|
}
|
|
3159
3194
|
const filename = path17.basename(absRawPath);
|
|
3160
3195
|
let bucketInfo;
|
|
3161
3196
|
try {
|
|
3162
|
-
|
|
3197
|
+
if (isSprintReport) {
|
|
3198
|
+
bucketInfo = deriveBucketFromReportPath(relRawPath);
|
|
3199
|
+
} else {
|
|
3200
|
+
bucketInfo = deriveBucket(filename);
|
|
3201
|
+
}
|
|
3163
3202
|
} catch (e) {
|
|
3164
3203
|
stderr(`wiki ingest: cannot determine bucket for ${rawPath}: ${e.message}
|
|
3165
3204
|
`);
|
|
@@ -3202,16 +3241,26 @@ async function wikiIngestHandler(opts) {
|
|
|
3202
3241
|
}
|
|
3203
3242
|
const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
|
|
3204
3243
|
const pageExists = fs17.existsSync(pagePath);
|
|
3205
|
-
|
|
3206
|
-
|
|
3244
|
+
let existingPage;
|
|
3245
|
+
if (pageExists) {
|
|
3207
3246
|
try {
|
|
3208
3247
|
const existingPageContent = fs17.readFileSync(pagePath, "utf8");
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3248
|
+
existingPage = parsePage(existingPageContent);
|
|
3249
|
+
} catch {
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
if (existingPage !== void 0 && currentSha !== "") {
|
|
3253
|
+
let isNoOp = false;
|
|
3254
|
+
try {
|
|
3255
|
+
if (isSprintReport) {
|
|
3256
|
+
if (existingPage.last_report_ingest_commit === currentSha) {
|
|
3213
3257
|
isNoOp = true;
|
|
3214
3258
|
}
|
|
3259
|
+
} else {
|
|
3260
|
+
if (existingPage.last_ingest_commit === currentSha) {
|
|
3261
|
+
const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
|
|
3262
|
+
if (contentUnchanged) isNoOp = true;
|
|
3263
|
+
}
|
|
3215
3264
|
}
|
|
3216
3265
|
} catch {
|
|
3217
3266
|
}
|
|
@@ -3223,36 +3272,53 @@ async function wikiIngestHandler(opts) {
|
|
|
3223
3272
|
}
|
|
3224
3273
|
}
|
|
3225
3274
|
const action = pageExists ? "update" : "create";
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
const existingPage = parsePage(existingPageContent);
|
|
3231
|
-
existingLastContradictSha = existingPage.last_contradict_sha;
|
|
3232
|
-
} catch {
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3275
|
+
const timestamp = now();
|
|
3276
|
+
const existingLastContradictSha = existingPage?.last_contradict_sha;
|
|
3277
|
+
const existingReportRawPath = existingPage?.report_raw_path;
|
|
3278
|
+
const existingLastReportIngestCommit = existingPage?.last_report_ingest_commit;
|
|
3235
3279
|
const parent = buildParentRef2(fm);
|
|
3236
3280
|
const children = buildChildrenRefs2(fm);
|
|
3237
|
-
const timestamp = now();
|
|
3238
3281
|
const wikiPage = {
|
|
3239
3282
|
type,
|
|
3240
3283
|
id,
|
|
3241
|
-
parent,
|
|
3242
|
-
children,
|
|
3243
|
-
status: String(fm["status"] ?? ""),
|
|
3244
|
-
remote_id: String(fm["remote_id"] ?? ""),
|
|
3245
|
-
raw_path
|
|
3284
|
+
parent: isSprintReport ? existingPage?.parent ?? "" : parent,
|
|
3285
|
+
children: isSprintReport ? existingPage?.children ?? [] : children,
|
|
3286
|
+
status: isSprintReport ? existingPage?.status ?? String(fm["status"] ?? "") : String(fm["status"] ?? ""),
|
|
3287
|
+
remote_id: isSprintReport ? existingPage?.remote_id ?? String(fm["remote_id"] ?? "") : String(fm["remote_id"] ?? ""),
|
|
3288
|
+
// raw_path tracks the plan file path; for report-only ingest preserve existing or use relRawPath as fallback
|
|
3289
|
+
raw_path: isSprintReport ? existingPage?.raw_path ?? relRawPath : relRawPath,
|
|
3246
3290
|
last_ingest: timestamp,
|
|
3247
|
-
last_ingest_commit
|
|
3291
|
+
// last_ingest_commit tracks the plan source; preserve when re-ingesting report
|
|
3292
|
+
last_ingest_commit: isSprintReport ? existingPage?.last_ingest_commit ?? "" : currentSha,
|
|
3248
3293
|
repo,
|
|
3249
3294
|
// Carry forward last_contradict_sha so Phase 4 idempotency survives re-ingest
|
|
3250
3295
|
...existingLastContradictSha !== void 0 ? { last_contradict_sha: existingLastContradictSha } : {},
|
|
3251
|
-
// Hierarchy keys (§11.7): read from raw fm
|
|
3252
|
-
...typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
|
|
3253
|
-
...typeof fm["sprint_cleargate_id"] === "string" ? { sprint_cleargate_id: fm["sprint_cleargate_id"] } : {}
|
|
3296
|
+
// Hierarchy keys (§11.7): read from raw fm for plan ingest; preserve for report ingest
|
|
3297
|
+
...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"] } : {},
|
|
3298
|
+
...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"] } : {},
|
|
3299
|
+
// CR-063 sprint-report fields
|
|
3300
|
+
report_raw_path: isSprintReport ? relRawPath : existingReportRawPath ?? void 0,
|
|
3301
|
+
last_report_ingest_commit: isSprintReport ? currentSha : existingLastReportIngestCommit ?? void 0
|
|
3254
3302
|
};
|
|
3255
|
-
|
|
3303
|
+
let existingPageBody = "";
|
|
3304
|
+
if (existingPage !== void 0 && pageExists) {
|
|
3305
|
+
try {
|
|
3306
|
+
const existingPageContent = fs17.readFileSync(pagePath, "utf8");
|
|
3307
|
+
const lines = existingPageContent.split("\n");
|
|
3308
|
+
let closingDash = -1;
|
|
3309
|
+
for (let i = 1; i < lines.length; i++) {
|
|
3310
|
+
if (lines[i] === "---") {
|
|
3311
|
+
closingDash = i;
|
|
3312
|
+
break;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
if (closingDash !== -1) {
|
|
3316
|
+
existingPageBody = lines.slice(closingDash + 1).join("\n").replace(/^\n/, "");
|
|
3317
|
+
}
|
|
3318
|
+
} catch {
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
const pageBody = buildPageBody2({ id, fm, body, isSprintReport, existingPageBody });
|
|
3256
3322
|
const pageContent = serializePage(wikiPage, pageBody);
|
|
3257
3323
|
fs17.mkdirSync(pageDir, { recursive: true });
|
|
3258
3324
|
fs17.writeFileSync(pagePath, pageContent, "utf8");
|
|
@@ -3330,7 +3396,37 @@ function buildChildrenRefs2(fm) {
|
|
|
3330
3396
|
return `[[${s}]]`;
|
|
3331
3397
|
});
|
|
3332
3398
|
}
|
|
3399
|
+
function extractReportBlock(pageBody) {
|
|
3400
|
+
const beginMarker = "<!-- BEGIN sprint-report -->";
|
|
3401
|
+
const endMarker = "<!-- END sprint-report -->";
|
|
3402
|
+
const beginIdx = pageBody.indexOf(beginMarker);
|
|
3403
|
+
const endIdx = pageBody.indexOf(endMarker);
|
|
3404
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) return void 0;
|
|
3405
|
+
return pageBody.slice(beginIdx, endIdx + endMarker.length);
|
|
3406
|
+
}
|
|
3407
|
+
function extractPlanStub(pageBody) {
|
|
3408
|
+
const beginMarker = "<!-- BEGIN sprint-report -->";
|
|
3409
|
+
const beginIdx = pageBody.indexOf(beginMarker);
|
|
3410
|
+
if (beginIdx === -1) return pageBody;
|
|
3411
|
+
return pageBody.slice(0, beginIdx);
|
|
3412
|
+
}
|
|
3333
3413
|
function buildPageBody2(item) {
|
|
3414
|
+
const { isSprintReport, existingPageBody } = item;
|
|
3415
|
+
if (isSprintReport) {
|
|
3416
|
+
const planStub = existingPageBody ? extractPlanStub(existingPageBody) : buildPlanStub(item);
|
|
3417
|
+
const reportBlock = buildReportBlock(item.body);
|
|
3418
|
+
return planStub + reportBlock;
|
|
3419
|
+
} else {
|
|
3420
|
+
const planStub = buildPlanStub(item);
|
|
3421
|
+
const existingReportBlock = existingPageBody ? extractReportBlock(existingPageBody) : void 0;
|
|
3422
|
+
if (existingReportBlock !== void 0) {
|
|
3423
|
+
const stub = planStub.trimEnd() + "\n\n";
|
|
3424
|
+
return stub + existingReportBlock + "\n";
|
|
3425
|
+
}
|
|
3426
|
+
return planStub;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
function buildPlanStub(item) {
|
|
3334
3430
|
const title = String(item.fm["title"] ?? item.id);
|
|
3335
3431
|
const summary = String(
|
|
3336
3432
|
item.fm["description"] ?? item.body.split("\n")[0] ?? "No summary available."
|
|
@@ -3354,6 +3450,16 @@ function buildPageBody2(item) {
|
|
|
3354
3450
|
""
|
|
3355
3451
|
].join("\n");
|
|
3356
3452
|
}
|
|
3453
|
+
function buildReportBlock(reportBody) {
|
|
3454
|
+
return [
|
|
3455
|
+
"<!-- BEGIN sprint-report -->",
|
|
3456
|
+
"## Sprint Report",
|
|
3457
|
+
"",
|
|
3458
|
+
reportBody.trim(),
|
|
3459
|
+
"<!-- END sprint-report -->",
|
|
3460
|
+
""
|
|
3461
|
+
].join("\n");
|
|
3462
|
+
}
|
|
3357
3463
|
function appendLogEntry(wikiRoot, entry) {
|
|
3358
3464
|
const logPath = path17.join(wikiRoot, "log.md");
|
|
3359
3465
|
const logEntry = [
|
|
@@ -5815,7 +5921,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
5815
5921
|
throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
|
|
5816
5922
|
}
|
|
5817
5923
|
const existing = coerceCachedGate(fm["cached_gate_result"]);
|
|
5818
|
-
if (existing && JSON.stringify(existing) === JSON.stringify(newResult)) {
|
|
5924
|
+
if (existing && existing.pass === newResult.pass && JSON.stringify(existing.failing_criteria) === JSON.stringify(newResult.failing_criteria)) {
|
|
5819
5925
|
return;
|
|
5820
5926
|
}
|
|
5821
5927
|
const newFm = {};
|
|
@@ -7029,6 +7135,10 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
|
|
|
7029
7135
|
if (!absPath) {
|
|
7030
7136
|
continue;
|
|
7031
7137
|
}
|
|
7138
|
+
if (absPath.includes(`${path31.sep}archive${path31.sep}`)) {
|
|
7139
|
+
result.skipped.push(id);
|
|
7140
|
+
continue;
|
|
7141
|
+
}
|
|
7032
7142
|
let status = "";
|
|
7033
7143
|
try {
|
|
7034
7144
|
const raw = fs30.readFileSync(absPath, "utf8");
|
|
@@ -10312,6 +10422,19 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
10312
10422
|
async function handlePush(filePath, ctx) {
|
|
10313
10423
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
10314
10424
|
const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(projectRoot, filePath);
|
|
10425
|
+
if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
|
|
10426
|
+
if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
|
|
10427
|
+
stderr(
|
|
10428
|
+
`Error: path not in allowlist. Only sprint report files are accepted from sprint-runs/.
|
|
10429
|
+
Allowed: .cleargate/sprint-runs/SPRINT-NN/REPORT.md
|
|
10430
|
+
.cleargate/sprint-runs/SPRINT-NN/SPRINT-NN_REPORT.md
|
|
10431
|
+
Got: "${resolvedPath}"
|
|
10432
|
+
`
|
|
10433
|
+
);
|
|
10434
|
+
exit(2);
|
|
10435
|
+
return;
|
|
10436
|
+
}
|
|
10437
|
+
}
|
|
10315
10438
|
let rawContent;
|
|
10316
10439
|
try {
|
|
10317
10440
|
rawContent = await fsPromises11.readFile(resolvedPath, "utf8");
|
|
@@ -10341,7 +10464,7 @@ async function handlePush(filePath, ctx) {
|
|
|
10341
10464
|
return;
|
|
10342
10465
|
}
|
|
10343
10466
|
const itemId = getItemId4(fm);
|
|
10344
|
-
const type =
|
|
10467
|
+
const type = getItemTypeWithPathOverride(resolvedPath, fm);
|
|
10345
10468
|
if (!type) {
|
|
10346
10469
|
stderr(`Error: cannot determine item type from frontmatter in "${resolvedPath}".
|
|
10347
10470
|
`);
|
|
@@ -10354,6 +10477,9 @@ async function handlePush(filePath, ctx) {
|
|
|
10354
10477
|
if (h1) payloadForPush["title"] = h1;
|
|
10355
10478
|
}
|
|
10356
10479
|
payloadForPush["body"] = body;
|
|
10480
|
+
if (payloadForPush["origin"] === void 0) {
|
|
10481
|
+
payloadForPush["origin"] = "cleargate-cli";
|
|
10482
|
+
}
|
|
10357
10483
|
const mcp2 = await resolveMcp();
|
|
10358
10484
|
let result;
|
|
10359
10485
|
try {
|
|
@@ -10473,17 +10599,20 @@ async function writeAtomic8(filePath, content) {
|
|
|
10473
10599
|
await fsPromises11.rename(tmpPath, filePath);
|
|
10474
10600
|
}
|
|
10475
10601
|
function getItemId4(fm) {
|
|
10476
|
-
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
10602
|
+
for (const key of ["story_id", "epic_id", "proposal_id", "sprint_id", "cr_id", "bug_id"]) {
|
|
10477
10603
|
const val = fm[key];
|
|
10478
10604
|
if (typeof val === "string" && val) return val;
|
|
10479
10605
|
}
|
|
10480
10606
|
return "unknown";
|
|
10481
10607
|
}
|
|
10608
|
+
var SPRINT_RUNS_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]/;
|
|
10609
|
+
var SPRINT_REPORT_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]SPRINT-\d{2,}[\\/](REPORT|SPRINT-\d{2,}_REPORT)\.md$/;
|
|
10482
10610
|
function getItemType2(fm) {
|
|
10483
10611
|
const typeMap = {
|
|
10484
10612
|
story_id: "story",
|
|
10485
10613
|
epic_id: "epic",
|
|
10486
10614
|
proposal_id: "proposal",
|
|
10615
|
+
sprint_id: "sprint",
|
|
10487
10616
|
cr_id: "cr",
|
|
10488
10617
|
bug_id: "bug"
|
|
10489
10618
|
};
|
|
@@ -10492,6 +10621,10 @@ function getItemType2(fm) {
|
|
|
10492
10621
|
}
|
|
10493
10622
|
return null;
|
|
10494
10623
|
}
|
|
10624
|
+
function getItemTypeWithPathOverride(localPath, fm) {
|
|
10625
|
+
if (SPRINT_REPORT_PATH_REGEX.test(localPath)) return "sprint_report";
|
|
10626
|
+
return getItemType2(fm);
|
|
10627
|
+
}
|
|
10495
10628
|
|
|
10496
10629
|
// src/commands/conflicts.ts
|
|
10497
10630
|
import * as fsPromises12 from "fs/promises";
|
|
@@ -10918,8 +11051,25 @@ var AuthFetcher = class {
|
|
|
10918
11051
|
}
|
|
10919
11052
|
};
|
|
10920
11053
|
|
|
11054
|
+
// src/auth/service-token-fetcher.ts
|
|
11055
|
+
var ServiceTokenFetcher = class {
|
|
11056
|
+
constructor(token) {
|
|
11057
|
+
this.token = token;
|
|
11058
|
+
}
|
|
11059
|
+
token;
|
|
11060
|
+
async getAccessToken() {
|
|
11061
|
+
return this.token;
|
|
11062
|
+
}
|
|
11063
|
+
};
|
|
11064
|
+
|
|
10921
11065
|
// src/commands/mcp-serve.ts
|
|
10922
11066
|
var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
|
|
11067
|
+
var ServiceToken401Error = class extends Error {
|
|
11068
|
+
constructor() {
|
|
11069
|
+
super("service-token-401");
|
|
11070
|
+
this.name = "ServiceToken401Error";
|
|
11071
|
+
}
|
|
11072
|
+
};
|
|
10923
11073
|
async function mcpServeHandler(opts) {
|
|
10924
11074
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
10925
11075
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
@@ -10929,32 +11079,44 @@ async function mcpServeHandler(opts) {
|
|
|
10929
11079
|
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
|
|
10930
11080
|
});
|
|
10931
11081
|
const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
|
|
10932
|
-
const
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
11082
|
+
const serviceToken = process.env["CLEARGATE_SERVICE_TOKEN"] ?? "";
|
|
11083
|
+
let fetcher;
|
|
11084
|
+
let isServiceTokenMode;
|
|
11085
|
+
if (serviceToken.length > 0) {
|
|
11086
|
+
isServiceTokenMode = true;
|
|
11087
|
+
fetcher = new ServiceTokenFetcher(serviceToken);
|
|
11088
|
+
stderr("cleargate mcp serve: auth mode = service-token\n");
|
|
11089
|
+
} else {
|
|
11090
|
+
isServiceTokenMode = false;
|
|
11091
|
+
const store = await (opts.createStore ?? createTokenStore)({
|
|
11092
|
+
...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
|
|
11093
|
+
...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
|
|
11094
|
+
});
|
|
11095
|
+
const authFetcher = new AuthFetcher({
|
|
11096
|
+
baseUrl,
|
|
11097
|
+
loadRefresh: () => store.load(opts.profile),
|
|
11098
|
+
saveRefresh: (t) => store.save(opts.profile, t),
|
|
11099
|
+
...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
|
|
11100
|
+
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
11101
|
+
});
|
|
11102
|
+
fetcher = authFetcher;
|
|
11103
|
+
stderr("cleargate mcp serve: auth mode = keychain-refresh\n");
|
|
11104
|
+
try {
|
|
11105
|
+
await authFetcher.getAccessToken();
|
|
11106
|
+
} catch (err) {
|
|
11107
|
+
if (err instanceof RefreshError) {
|
|
11108
|
+
stderr(
|
|
11109
|
+
`cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
|
|
10949
11110
|
`
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
11111
|
+
);
|
|
11112
|
+
} else {
|
|
11113
|
+
stderr(
|
|
11114
|
+
`cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
|
|
10954
11115
|
`
|
|
10955
|
-
|
|
11116
|
+
);
|
|
11117
|
+
}
|
|
11118
|
+
return exit(1);
|
|
10956
11119
|
}
|
|
10957
|
-
return exit(1);
|
|
10958
11120
|
}
|
|
10959
11121
|
const inputStream = opts.stdin ?? process.stdin;
|
|
10960
11122
|
const rl = readline5.createInterface({
|
|
@@ -10965,8 +11127,23 @@ async function mcpServeHandler(opts) {
|
|
|
10965
11127
|
for await (const line of rl) {
|
|
10966
11128
|
if (!line.trim()) continue;
|
|
10967
11129
|
try {
|
|
10968
|
-
await proxyOne(
|
|
11130
|
+
await proxyOne(
|
|
11131
|
+
line,
|
|
11132
|
+
baseUrl,
|
|
11133
|
+
fetcher,
|
|
11134
|
+
isServiceTokenMode,
|
|
11135
|
+
fetchFn,
|
|
11136
|
+
stdout,
|
|
11137
|
+
stderr
|
|
11138
|
+
);
|
|
10969
11139
|
} catch (err) {
|
|
11140
|
+
if (err instanceof ServiceToken401Error) {
|
|
11141
|
+
stderr(
|
|
11142
|
+
`cleargate mcp serve: CLEARGATE_SERVICE_TOKEN rejected by /mcp (401). Issue a new token in the admin console: Tokens \u2192 Issue \u2192 copy snippet.
|
|
11143
|
+
`
|
|
11144
|
+
);
|
|
11145
|
+
return exit(1);
|
|
11146
|
+
}
|
|
10970
11147
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10971
11148
|
stderr(`cleargate mcp serve: proxy error: ${errMsg}
|
|
10972
11149
|
`);
|
|
@@ -10983,7 +11160,7 @@ async function mcpServeHandler(opts) {
|
|
|
10983
11160
|
}
|
|
10984
11161
|
}
|
|
10985
11162
|
}
|
|
10986
|
-
async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
11163
|
+
async function proxyOne(line, baseUrl, fetcher, isServiceTokenMode, fetchFn, stdout, stderr) {
|
|
10987
11164
|
let parsed;
|
|
10988
11165
|
try {
|
|
10989
11166
|
parsed = JSON.parse(line);
|
|
@@ -10996,6 +11173,9 @@ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
|
10996
11173
|
let access = await fetcher.getAccessToken();
|
|
10997
11174
|
let res = await postFrame(baseUrl, line, access, fetchFn);
|
|
10998
11175
|
if (res.status === 401) {
|
|
11176
|
+
if (isServiceTokenMode) {
|
|
11177
|
+
throw new ServiceToken401Error();
|
|
11178
|
+
}
|
|
10999
11179
|
fetcher.invalidate();
|
|
11000
11180
|
access = await fetcher.getAccessToken();
|
|
11001
11181
|
res = await postFrame(baseUrl, line, access, fetchFn);
|
|
@@ -11119,7 +11299,7 @@ program.command("init").description("initialise a repo with ClearGate scaffold (
|
|
|
11119
11299
|
await initHandler({ force: opts.force ?? false, yes: opts.yes ?? false, pin: opts.pin, fromSource: opts.fromSource });
|
|
11120
11300
|
});
|
|
11121
11301
|
program.command("whoami").description("print the currently authenticated agent identity").option("--json", "CR-011: emit membership state as JSON (no network call)").action(async (opts) => {
|
|
11122
|
-
const { whoamiHandler } = await import("./whoami-
|
|
11302
|
+
const { whoamiHandler } = await import("./whoami-GQTFZHFQ.js");
|
|
11123
11303
|
const parentOpts = program.opts();
|
|
11124
11304
|
await whoamiHandler({
|
|
11125
11305
|
profile: parentOpts.profile,
|