cleargate 0.11.5 → 0.13.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 +15 -15
- 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-HZPJ5QX4.js → chunk-EG6YGT2O.js} +315 -33
- package/dist/chunk-EG6YGT2O.js.map +1 -0
- package/dist/cli.cjs +855 -359
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +316 -108
- package/dist/cli.js.map +1 -1
- package/dist/lib/lifecycle-reconcile.cjs +318 -34
- package/dist/lib/lifecycle-reconcile.cjs.map +1 -1
- package/dist/lib/lifecycle-reconcile.d.cts +55 -4
- package/dist/lib/lifecycle-reconcile.d.ts +55 -4
- package/dist/lib/lifecycle-reconcile.js +7 -3
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +6 -4
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +8 -4
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +138 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +162 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
- package/dist/templates/cleargate-planning/CLAUDE.md +4 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +15 -15
- package/package.json +8 -9
- package/templates/cleargate-planning/.claude/agents/architect.md +6 -4
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
- package/templates/cleargate-planning/.claude/agents/developer.md +8 -4
- package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +138 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +162 -0
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
- package/templates/cleargate-planning/CLAUDE.md +4 -0
- package/templates/cleargate-planning/MANIFEST.json +15 -15
- package/dist/chunk-HZPJ5QX4.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
checkVerbMismatch,
|
|
4
4
|
parseFrontmatter,
|
|
5
5
|
reconcileDecomposition,
|
|
6
|
-
reconcileLifecycle
|
|
7
|
-
|
|
6
|
+
reconcileLifecycle,
|
|
7
|
+
walkActiveParents
|
|
8
|
+
} from "./chunk-EG6YGT2O.js";
|
|
8
9
|
import {
|
|
9
10
|
AcquireError,
|
|
10
11
|
acquireAccessToken,
|
|
@@ -22,7 +23,7 @@ import { Command } from "commander";
|
|
|
22
23
|
// package.json
|
|
23
24
|
var package_default = {
|
|
24
25
|
name: "cleargate",
|
|
25
|
-
version: "0.
|
|
26
|
+
version: "0.13.0",
|
|
26
27
|
private: false,
|
|
27
28
|
type: "module",
|
|
28
29
|
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.",
|
|
@@ -69,12 +70,11 @@ var package_default = {
|
|
|
69
70
|
build: "tsup",
|
|
70
71
|
dev: "tsup --watch",
|
|
71
72
|
typecheck: "tsc --noEmit",
|
|
72
|
-
test: "tsx --test --test-reporter=spec 'test/**/*.node.test.ts'",
|
|
73
|
-
"test:file": "tsx --test --test-reporter=spec",
|
|
74
|
-
"test:
|
|
75
|
-
"test:
|
|
76
|
-
"
|
|
77
|
-
"test:node:file": "tsx --test --test-reporter=spec"
|
|
73
|
+
test: "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec 'test/**/*.node.test.ts' '!test/fixtures/**'",
|
|
74
|
+
"test:file": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec",
|
|
75
|
+
"test:node": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec 'test/**/*.node.test.ts' '!test/fixtures/**'",
|
|
76
|
+
"test:node:file": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec",
|
|
77
|
+
"check:no-vitest": `node -e "const r=require('child_process').execSync('grep -rE \\"\\\\b(vitest|vi\\\\.fn|vi\\\\.mock|vi\\\\.spyOn|vi\\\\.stubGlobal|vi\\\\.useFakeTimers|vi\\\\.useRealTimers|vi\\\\.advanceTimersByTime|vi\\\\.hoisted)\\\\b\\" --include=\\"*.ts\\" --include=\\"*.js\\" --include=\\"*.mjs\\" --exclude-dir=test/fixtures . 2>/dev/null || true', {encoding:'utf8'}); if(r.trim()){console.error('vitest residue detected:\\\\n'+r); process.exit(1)} console.log('no vitest residue')"`
|
|
78
78
|
},
|
|
79
79
|
dependencies: {
|
|
80
80
|
"@napi-rs/keyring": "^1.2.0",
|
|
@@ -89,10 +89,10 @@ var package_default = {
|
|
|
89
89
|
"@types/js-yaml": "^4.0.9",
|
|
90
90
|
"@types/node": "^24.0.0",
|
|
91
91
|
"@types/pg": "^8.11.10",
|
|
92
|
+
"ts-morph": "28.0.0",
|
|
92
93
|
tsup: "^8",
|
|
93
94
|
tsx: "^4.21.0",
|
|
94
|
-
typescript: "^5.8.0"
|
|
95
|
-
vitest: "^2.1.0"
|
|
95
|
+
typescript: "^5.8.0"
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
98
|
|
|
@@ -1485,6 +1485,24 @@ var PREFIX_MAP = [
|
|
|
1485
1485
|
{ prefix: "BUG-", type: "bug", bucket: "bugs" },
|
|
1486
1486
|
{ prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
|
|
1487
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
|
+
}
|
|
1488
1506
|
function deriveBucket(filename) {
|
|
1489
1507
|
const base = filename.includes("/") ? filename.split("/").pop() : filename;
|
|
1490
1508
|
const stem = base.endsWith(".md") ? base.slice(0, -3) : base;
|
|
@@ -1605,6 +1623,12 @@ function serializePage(page, body) {
|
|
|
1605
1623
|
if (page.sprint_cleargate_id !== void 0) {
|
|
1606
1624
|
lines.push(`sprint_cleargate_id: "${page.sprint_cleargate_id}"`);
|
|
1607
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
|
+
}
|
|
1608
1632
|
lines.push("---");
|
|
1609
1633
|
const fm = lines.join("\n");
|
|
1610
1634
|
return `${fm}
|
|
@@ -1627,7 +1651,9 @@ function parsePage(raw) {
|
|
|
1627
1651
|
const last_contradict_sha = fm["last_contradict_sha"] !== void 0 ? String(fm["last_contradict_sha"]) : void 0;
|
|
1628
1652
|
const parent_cleargate_id = fm["parent_cleargate_id"] !== void 0 ? String(fm["parent_cleargate_id"]) : void 0;
|
|
1629
1653
|
const sprint_cleargate_id = fm["sprint_cleargate_id"] !== void 0 ? String(fm["sprint_cleargate_id"]) : void 0;
|
|
1630
|
-
|
|
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 };
|
|
1631
1657
|
}
|
|
1632
1658
|
function parseFmRaw(raw) {
|
|
1633
1659
|
const lines = raw.split("\n");
|
|
@@ -3146,28 +3172,33 @@ async function wikiIngestHandler(opts) {
|
|
|
3146
3172
|
const rawPath = opts.rawPath;
|
|
3147
3173
|
const absRawPath = path17.isAbsolute(rawPath) ? rawPath : path17.resolve(cwd, rawPath);
|
|
3148
3174
|
const relRawPath = path17.relative(cwd, absRawPath).replace(/\\/g, "/");
|
|
3149
|
-
const
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
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/
|
|
3155
3182
|
`);
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
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)
|
|
3163
3189
|
`);
|
|
3164
|
-
|
|
3165
|
-
|
|
3190
|
+
exit(0);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3166
3193
|
}
|
|
3167
3194
|
const filename = path17.basename(absRawPath);
|
|
3168
3195
|
let bucketInfo;
|
|
3169
3196
|
try {
|
|
3170
|
-
|
|
3197
|
+
if (isSprintReport) {
|
|
3198
|
+
bucketInfo = deriveBucketFromReportPath(relRawPath);
|
|
3199
|
+
} else {
|
|
3200
|
+
bucketInfo = deriveBucket(filename);
|
|
3201
|
+
}
|
|
3171
3202
|
} catch (e) {
|
|
3172
3203
|
stderr(`wiki ingest: cannot determine bucket for ${rawPath}: ${e.message}
|
|
3173
3204
|
`);
|
|
@@ -3210,16 +3241,26 @@ async function wikiIngestHandler(opts) {
|
|
|
3210
3241
|
}
|
|
3211
3242
|
const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
|
|
3212
3243
|
const pageExists = fs17.existsSync(pagePath);
|
|
3213
|
-
|
|
3214
|
-
|
|
3244
|
+
let existingPage;
|
|
3245
|
+
if (pageExists) {
|
|
3215
3246
|
try {
|
|
3216
3247
|
const existingPageContent = fs17.readFileSync(pagePath, "utf8");
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
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) {
|
|
3221
3257
|
isNoOp = true;
|
|
3222
3258
|
}
|
|
3259
|
+
} else {
|
|
3260
|
+
if (existingPage.last_ingest_commit === currentSha) {
|
|
3261
|
+
const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
|
|
3262
|
+
if (contentUnchanged) isNoOp = true;
|
|
3263
|
+
}
|
|
3223
3264
|
}
|
|
3224
3265
|
} catch {
|
|
3225
3266
|
}
|
|
@@ -3231,36 +3272,53 @@ async function wikiIngestHandler(opts) {
|
|
|
3231
3272
|
}
|
|
3232
3273
|
}
|
|
3233
3274
|
const action = pageExists ? "update" : "create";
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
const existingPage = parsePage(existingPageContent);
|
|
3239
|
-
existingLastContradictSha = existingPage.last_contradict_sha;
|
|
3240
|
-
} catch {
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
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;
|
|
3243
3279
|
const parent = buildParentRef2(fm);
|
|
3244
3280
|
const children = buildChildrenRefs2(fm);
|
|
3245
|
-
const timestamp = now();
|
|
3246
3281
|
const wikiPage = {
|
|
3247
3282
|
type,
|
|
3248
3283
|
id,
|
|
3249
|
-
parent,
|
|
3250
|
-
children,
|
|
3251
|
-
status: String(fm["status"] ?? ""),
|
|
3252
|
-
remote_id: String(fm["remote_id"] ?? ""),
|
|
3253
|
-
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,
|
|
3254
3290
|
last_ingest: timestamp,
|
|
3255
|
-
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,
|
|
3256
3293
|
repo,
|
|
3257
3294
|
// Carry forward last_contradict_sha so Phase 4 idempotency survives re-ingest
|
|
3258
3295
|
...existingLastContradictSha !== void 0 ? { last_contradict_sha: existingLastContradictSha } : {},
|
|
3259
|
-
// Hierarchy keys (§11.7): read from raw fm
|
|
3260
|
-
...typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
|
|
3261
|
-
...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
|
|
3262
3302
|
};
|
|
3263
|
-
|
|
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 });
|
|
3264
3322
|
const pageContent = serializePage(wikiPage, pageBody);
|
|
3265
3323
|
fs17.mkdirSync(pageDir, { recursive: true });
|
|
3266
3324
|
fs17.writeFileSync(pagePath, pageContent, "utf8");
|
|
@@ -3338,7 +3396,37 @@ function buildChildrenRefs2(fm) {
|
|
|
3338
3396
|
return `[[${s}]]`;
|
|
3339
3397
|
});
|
|
3340
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
|
+
}
|
|
3341
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) {
|
|
3342
3430
|
const title = String(item.fm["title"] ?? item.id);
|
|
3343
3431
|
const summary = String(
|
|
3344
3432
|
item.fm["description"] ?? item.body.split("\n")[0] ?? "No summary available."
|
|
@@ -3362,6 +3450,16 @@ function buildPageBody2(item) {
|
|
|
3362
3450
|
""
|
|
3363
3451
|
].join("\n");
|
|
3364
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
|
+
}
|
|
3365
3463
|
function appendLogEntry(wikiRoot, entry) {
|
|
3366
3464
|
const logPath = path17.join(wikiRoot, "log.md");
|
|
3367
3465
|
const logEntry = [
|
|
@@ -5823,7 +5921,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
5823
5921
|
throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
|
|
5824
5922
|
}
|
|
5825
5923
|
const existing = coerceCachedGate(fm["cached_gate_result"]);
|
|
5826
|
-
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)) {
|
|
5827
5925
|
return;
|
|
5828
5926
|
}
|
|
5829
5927
|
const newFm = {};
|
|
@@ -6365,22 +6463,50 @@ function reconcileLifecycleCliHandler(opts, cli) {
|
|
|
6365
6463
|
});
|
|
6366
6464
|
if (result.drift.length === 0) {
|
|
6367
6465
|
stdoutFn(`lifecycle: clean (${result.clean} artifacts reconciled)`);
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6466
|
+
} else {
|
|
6467
|
+
stderrFn(`lifecycle: DRIFT detected (${result.drift.length} unreconciled artifacts):`);
|
|
6468
|
+
for (const item of result.drift) {
|
|
6469
|
+
stderrFn(
|
|
6470
|
+
` DRIFT: ${item.id} status=${item.actual_status ?? "missing"} in ${item.in_archive ? "archive" : "pending-sync"}, expected ${item.expected_status} (commit ${item.commit_shas[0] ?? "unknown"})`
|
|
6471
|
+
);
|
|
6472
|
+
stderrFn(
|
|
6473
|
+
` Remediation: git mv .cleargate/delivery/pending-sync/${item.file_path?.replace("pending-sync/", "") ?? item.id + "_*.md"} .cleargate/delivery/archive/ && update status: ${item.expected_status}`
|
|
6474
|
+
);
|
|
6475
|
+
}
|
|
6476
|
+
if (!opts.parents) {
|
|
6477
|
+
return exitFn(1);
|
|
6478
|
+
}
|
|
6378
6479
|
}
|
|
6379
|
-
return exitFn(1);
|
|
6380
6480
|
} catch (err) {
|
|
6381
6481
|
stderrFn(`lifecycle reconciliation error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6382
|
-
|
|
6482
|
+
if (!opts.parents) {
|
|
6483
|
+
return exitFn(1);
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
if (opts.parents) {
|
|
6487
|
+
const archiveRoot = path31.join(deliveryRoot, "archive");
|
|
6488
|
+
walkActiveParents({ deliveryRoot, archiveRoot }).then((results) => {
|
|
6489
|
+
stdoutFn("Parent rollup audit (--parents):");
|
|
6490
|
+
for (const r of results) {
|
|
6491
|
+
if (r.verdict === "auto-flip") {
|
|
6492
|
+
stdoutFn(
|
|
6493
|
+
` ${r.parent_id} \u2713 proposed: Completed (${r.terminal_children.length}/${r.terminal_children.length} children Completed)`
|
|
6494
|
+
);
|
|
6495
|
+
} else if (r.verdict === "halt-partial" || r.verdict === "halt-zero-children") {
|
|
6496
|
+
stdoutFn(` ${r.parent_id} \u2717 ${r.verdict}: ${r.halt_reason ?? "no details"}`);
|
|
6497
|
+
} else if (r.verdict === "no-op") {
|
|
6498
|
+
} else {
|
|
6499
|
+
stdoutFn(` ${r.parent_id} ~ ${r.verdict}`);
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
exitFn(0);
|
|
6503
|
+
}).catch((err) => {
|
|
6504
|
+
stderrFn(`--parents audit error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6505
|
+
exitFn(0);
|
|
6506
|
+
});
|
|
6507
|
+
return;
|
|
6383
6508
|
}
|
|
6509
|
+
return exitFn(0);
|
|
6384
6510
|
}
|
|
6385
6511
|
function parseFileFrontmatter(raw) {
|
|
6386
6512
|
const lines = raw.split("\n");
|
|
@@ -7037,6 +7163,10 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
|
|
|
7037
7163
|
if (!absPath) {
|
|
7038
7164
|
continue;
|
|
7039
7165
|
}
|
|
7166
|
+
if (absPath.includes(`${path31.sep}archive${path31.sep}`)) {
|
|
7167
|
+
result.skipped.push(id);
|
|
7168
|
+
continue;
|
|
7169
|
+
}
|
|
7040
7170
|
let status = "";
|
|
7041
7171
|
try {
|
|
7042
7172
|
const raw = fs30.readFileSync(absPath, "utf8");
|
|
@@ -10244,6 +10374,7 @@ function getItemId3(fm) {
|
|
|
10244
10374
|
}
|
|
10245
10375
|
|
|
10246
10376
|
// src/commands/push.ts
|
|
10377
|
+
import * as fs40 from "fs";
|
|
10247
10378
|
import * as fsPromises11 from "fs/promises";
|
|
10248
10379
|
import * as path49 from "path";
|
|
10249
10380
|
async function pushHandler(fileOrId, opts = {}) {
|
|
@@ -10253,6 +10384,13 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
10253
10384
|
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
10254
10385
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
10255
10386
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
10387
|
+
const migrationLockPath = path49.join(projectRoot, ".cleargate", ".migration-lock");
|
|
10388
|
+
if (fs40.existsSync(migrationLockPath)) {
|
|
10389
|
+
stderr(`Error: CR-067 migration in progress (.migration-lock held); retry in 30s
|
|
10390
|
+
`);
|
|
10391
|
+
exit(75);
|
|
10392
|
+
return;
|
|
10393
|
+
}
|
|
10256
10394
|
const identity = resolveIdentity(projectRoot);
|
|
10257
10395
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
10258
10396
|
async function resolveMcp() {
|
|
@@ -10320,6 +10458,19 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
10320
10458
|
async function handlePush(filePath, ctx) {
|
|
10321
10459
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
10322
10460
|
const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(projectRoot, filePath);
|
|
10461
|
+
if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
|
|
10462
|
+
if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
|
|
10463
|
+
stderr(
|
|
10464
|
+
`Error: path not in allowlist. Only sprint report files are accepted from sprint-runs/.
|
|
10465
|
+
Allowed: .cleargate/sprint-runs/SPRINT-NN/REPORT.md
|
|
10466
|
+
.cleargate/sprint-runs/SPRINT-NN/SPRINT-NN_REPORT.md
|
|
10467
|
+
Got: "${resolvedPath}"
|
|
10468
|
+
`
|
|
10469
|
+
);
|
|
10470
|
+
exit(2);
|
|
10471
|
+
return;
|
|
10472
|
+
}
|
|
10473
|
+
}
|
|
10323
10474
|
let rawContent;
|
|
10324
10475
|
try {
|
|
10325
10476
|
rawContent = await fsPromises11.readFile(resolvedPath, "utf8");
|
|
@@ -10349,7 +10500,7 @@ async function handlePush(filePath, ctx) {
|
|
|
10349
10500
|
return;
|
|
10350
10501
|
}
|
|
10351
10502
|
const itemId = getItemId4(fm);
|
|
10352
|
-
const type =
|
|
10503
|
+
const type = getItemTypeWithPathOverride(resolvedPath, fm);
|
|
10353
10504
|
if (!type) {
|
|
10354
10505
|
stderr(`Error: cannot determine item type from frontmatter in "${resolvedPath}".
|
|
10355
10506
|
`);
|
|
@@ -10362,6 +10513,9 @@ async function handlePush(filePath, ctx) {
|
|
|
10362
10513
|
if (h1) payloadForPush["title"] = h1;
|
|
10363
10514
|
}
|
|
10364
10515
|
payloadForPush["body"] = body;
|
|
10516
|
+
if (payloadForPush["origin"] === void 0) {
|
|
10517
|
+
payloadForPush["origin"] = "cleargate-cli";
|
|
10518
|
+
}
|
|
10365
10519
|
const mcp2 = await resolveMcp();
|
|
10366
10520
|
let result;
|
|
10367
10521
|
try {
|
|
@@ -10481,17 +10635,20 @@ async function writeAtomic8(filePath, content) {
|
|
|
10481
10635
|
await fsPromises11.rename(tmpPath, filePath);
|
|
10482
10636
|
}
|
|
10483
10637
|
function getItemId4(fm) {
|
|
10484
|
-
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
10638
|
+
for (const key of ["story_id", "epic_id", "proposal_id", "sprint_id", "cr_id", "bug_id"]) {
|
|
10485
10639
|
const val = fm[key];
|
|
10486
10640
|
if (typeof val === "string" && val) return val;
|
|
10487
10641
|
}
|
|
10488
10642
|
return "unknown";
|
|
10489
10643
|
}
|
|
10644
|
+
var SPRINT_RUNS_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]/;
|
|
10645
|
+
var SPRINT_REPORT_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]SPRINT-\d{2,}[\\/](REPORT|SPRINT-\d{2,}_REPORT)\.md$/;
|
|
10490
10646
|
function getItemType2(fm) {
|
|
10491
10647
|
const typeMap = {
|
|
10492
10648
|
story_id: "story",
|
|
10493
10649
|
epic_id: "epic",
|
|
10494
10650
|
proposal_id: "proposal",
|
|
10651
|
+
sprint_id: "sprint",
|
|
10495
10652
|
cr_id: "cr",
|
|
10496
10653
|
bug_id: "bug"
|
|
10497
10654
|
};
|
|
@@ -10500,6 +10657,10 @@ function getItemType2(fm) {
|
|
|
10500
10657
|
}
|
|
10501
10658
|
return null;
|
|
10502
10659
|
}
|
|
10660
|
+
function getItemTypeWithPathOverride(localPath, fm) {
|
|
10661
|
+
if (SPRINT_REPORT_PATH_REGEX.test(localPath)) return "sprint_report";
|
|
10662
|
+
return getItemType2(fm);
|
|
10663
|
+
}
|
|
10503
10664
|
|
|
10504
10665
|
// src/commands/conflicts.ts
|
|
10505
10666
|
import * as fsPromises12 from "fs/promises";
|
|
@@ -10625,7 +10786,7 @@ function formatEntry(entry) {
|
|
|
10625
10786
|
}
|
|
10626
10787
|
|
|
10627
10788
|
// src/commands/admin-login.ts
|
|
10628
|
-
import * as
|
|
10789
|
+
import * as fs41 from "fs";
|
|
10629
10790
|
import * as path51 from "path";
|
|
10630
10791
|
import * as os7 from "os";
|
|
10631
10792
|
var DEFAULT_MCP_URL = "http://localhost:3000";
|
|
@@ -10639,10 +10800,10 @@ function resolveAuthFilePath(opts) {
|
|
|
10639
10800
|
}
|
|
10640
10801
|
function writeAdminAuth(filePath, token) {
|
|
10641
10802
|
const dir = path51.dirname(filePath);
|
|
10642
|
-
|
|
10803
|
+
fs41.mkdirSync(dir, { recursive: true });
|
|
10643
10804
|
const payload = JSON.stringify({ version: 1, token }, null, 2);
|
|
10644
|
-
|
|
10645
|
-
|
|
10805
|
+
fs41.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
|
|
10806
|
+
fs41.chmodSync(filePath, 384);
|
|
10646
10807
|
}
|
|
10647
10808
|
async function adminLoginHandler(opts = {}) {
|
|
10648
10809
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
@@ -10751,7 +10912,7 @@ async function adminLoginHandler(opts = {}) {
|
|
|
10751
10912
|
}
|
|
10752
10913
|
|
|
10753
10914
|
// src/commands/hotfix.ts
|
|
10754
|
-
import * as
|
|
10915
|
+
import * as fs42 from "fs";
|
|
10755
10916
|
import * as path52 from "path";
|
|
10756
10917
|
function defaultExit4(code) {
|
|
10757
10918
|
return process.exit(code);
|
|
@@ -10762,7 +10923,7 @@ function maxHotfixId(pendingDir) {
|
|
|
10762
10923
|
let max = 0;
|
|
10763
10924
|
let entries;
|
|
10764
10925
|
try {
|
|
10765
|
-
entries =
|
|
10926
|
+
entries = fs42.readdirSync(pendingDir);
|
|
10766
10927
|
} catch {
|
|
10767
10928
|
return 0;
|
|
10768
10929
|
}
|
|
@@ -10782,7 +10943,7 @@ function countActiveHotfixes(repoRoot) {
|
|
|
10782
10943
|
let count = 0;
|
|
10783
10944
|
let pendingEntries = [];
|
|
10784
10945
|
try {
|
|
10785
|
-
pendingEntries =
|
|
10946
|
+
pendingEntries = fs42.readdirSync(pendingDir);
|
|
10786
10947
|
} catch {
|
|
10787
10948
|
}
|
|
10788
10949
|
for (const entry of pendingEntries) {
|
|
@@ -10790,13 +10951,13 @@ function countActiveHotfixes(repoRoot) {
|
|
|
10790
10951
|
}
|
|
10791
10952
|
let archiveEntries = [];
|
|
10792
10953
|
try {
|
|
10793
|
-
archiveEntries =
|
|
10954
|
+
archiveEntries = fs42.readdirSync(archiveDir);
|
|
10794
10955
|
} catch {
|
|
10795
10956
|
}
|
|
10796
10957
|
for (const entry of archiveEntries) {
|
|
10797
10958
|
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
|
|
10798
10959
|
try {
|
|
10799
|
-
const stat =
|
|
10960
|
+
const stat = fs42.statSync(path52.join(archiveDir, entry));
|
|
10800
10961
|
if (stat.mtimeMs >= sevenDaysAgo) count++;
|
|
10801
10962
|
} catch {
|
|
10802
10963
|
}
|
|
@@ -10831,7 +10992,7 @@ function hotfixNewHandler(opts, cli) {
|
|
|
10831
10992
|
const templatePath = resolveTemplatePath(repoRoot);
|
|
10832
10993
|
let templateContent;
|
|
10833
10994
|
try {
|
|
10834
|
-
templateContent =
|
|
10995
|
+
templateContent = fs42.readFileSync(templatePath, "utf8");
|
|
10835
10996
|
} catch {
|
|
10836
10997
|
stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
|
|
10837
10998
|
return exitFn(2);
|
|
@@ -10841,8 +11002,8 @@ function hotfixNewHandler(opts, cli) {
|
|
|
10841
11002
|
const fileName = `${idStr}_${fileSlug}.md`;
|
|
10842
11003
|
const outPath = path52.join(pendingDir, fileName);
|
|
10843
11004
|
try {
|
|
10844
|
-
|
|
10845
|
-
|
|
11005
|
+
fs42.mkdirSync(pendingDir, { recursive: true });
|
|
11006
|
+
fs42.writeFileSync(outPath, content, "utf8");
|
|
10846
11007
|
} catch (err) {
|
|
10847
11008
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10848
11009
|
stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
|
|
@@ -10926,8 +11087,25 @@ var AuthFetcher = class {
|
|
|
10926
11087
|
}
|
|
10927
11088
|
};
|
|
10928
11089
|
|
|
11090
|
+
// src/auth/service-token-fetcher.ts
|
|
11091
|
+
var ServiceTokenFetcher = class {
|
|
11092
|
+
constructor(token) {
|
|
11093
|
+
this.token = token;
|
|
11094
|
+
}
|
|
11095
|
+
token;
|
|
11096
|
+
async getAccessToken() {
|
|
11097
|
+
return this.token;
|
|
11098
|
+
}
|
|
11099
|
+
};
|
|
11100
|
+
|
|
10929
11101
|
// src/commands/mcp-serve.ts
|
|
10930
11102
|
var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
|
|
11103
|
+
var ServiceToken401Error = class extends Error {
|
|
11104
|
+
constructor() {
|
|
11105
|
+
super("service-token-401");
|
|
11106
|
+
this.name = "ServiceToken401Error";
|
|
11107
|
+
}
|
|
11108
|
+
};
|
|
10931
11109
|
async function mcpServeHandler(opts) {
|
|
10932
11110
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
10933
11111
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
@@ -10937,32 +11115,44 @@ async function mcpServeHandler(opts) {
|
|
|
10937
11115
|
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
|
|
10938
11116
|
});
|
|
10939
11117
|
const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
|
|
10940
|
-
const
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
11118
|
+
const serviceToken = process.env["CLEARGATE_SERVICE_TOKEN"] ?? "";
|
|
11119
|
+
let fetcher;
|
|
11120
|
+
let isServiceTokenMode;
|
|
11121
|
+
if (serviceToken.length > 0) {
|
|
11122
|
+
isServiceTokenMode = true;
|
|
11123
|
+
fetcher = new ServiceTokenFetcher(serviceToken);
|
|
11124
|
+
stderr("cleargate mcp serve: auth mode = service-token\n");
|
|
11125
|
+
} else {
|
|
11126
|
+
isServiceTokenMode = false;
|
|
11127
|
+
const store = await (opts.createStore ?? createTokenStore)({
|
|
11128
|
+
...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
|
|
11129
|
+
...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
|
|
11130
|
+
});
|
|
11131
|
+
const authFetcher = new AuthFetcher({
|
|
11132
|
+
baseUrl,
|
|
11133
|
+
loadRefresh: () => store.load(opts.profile),
|
|
11134
|
+
saveRefresh: (t) => store.save(opts.profile, t),
|
|
11135
|
+
...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
|
|
11136
|
+
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
11137
|
+
});
|
|
11138
|
+
fetcher = authFetcher;
|
|
11139
|
+
stderr("cleargate mcp serve: auth mode = keychain-refresh\n");
|
|
11140
|
+
try {
|
|
11141
|
+
await authFetcher.getAccessToken();
|
|
11142
|
+
} catch (err) {
|
|
11143
|
+
if (err instanceof RefreshError) {
|
|
11144
|
+
stderr(
|
|
11145
|
+
`cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
|
|
10957
11146
|
`
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
11147
|
+
);
|
|
11148
|
+
} else {
|
|
11149
|
+
stderr(
|
|
11150
|
+
`cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
|
|
10962
11151
|
`
|
|
10963
|
-
|
|
11152
|
+
);
|
|
11153
|
+
}
|
|
11154
|
+
return exit(1);
|
|
10964
11155
|
}
|
|
10965
|
-
return exit(1);
|
|
10966
11156
|
}
|
|
10967
11157
|
const inputStream = opts.stdin ?? process.stdin;
|
|
10968
11158
|
const rl = readline5.createInterface({
|
|
@@ -10973,8 +11163,23 @@ async function mcpServeHandler(opts) {
|
|
|
10973
11163
|
for await (const line of rl) {
|
|
10974
11164
|
if (!line.trim()) continue;
|
|
10975
11165
|
try {
|
|
10976
|
-
await proxyOne(
|
|
11166
|
+
await proxyOne(
|
|
11167
|
+
line,
|
|
11168
|
+
baseUrl,
|
|
11169
|
+
fetcher,
|
|
11170
|
+
isServiceTokenMode,
|
|
11171
|
+
fetchFn,
|
|
11172
|
+
stdout,
|
|
11173
|
+
stderr
|
|
11174
|
+
);
|
|
10977
11175
|
} catch (err) {
|
|
11176
|
+
if (err instanceof ServiceToken401Error) {
|
|
11177
|
+
stderr(
|
|
11178
|
+
`cleargate mcp serve: CLEARGATE_SERVICE_TOKEN rejected by /mcp (401). Issue a new token in the admin console: Tokens \u2192 Issue \u2192 copy snippet.
|
|
11179
|
+
`
|
|
11180
|
+
);
|
|
11181
|
+
return exit(1);
|
|
11182
|
+
}
|
|
10978
11183
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10979
11184
|
stderr(`cleargate mcp serve: proxy error: ${errMsg}
|
|
10980
11185
|
`);
|
|
@@ -10991,7 +11196,7 @@ async function mcpServeHandler(opts) {
|
|
|
10991
11196
|
}
|
|
10992
11197
|
}
|
|
10993
11198
|
}
|
|
10994
|
-
async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
11199
|
+
async function proxyOne(line, baseUrl, fetcher, isServiceTokenMode, fetchFn, stdout, stderr) {
|
|
10995
11200
|
let parsed;
|
|
10996
11201
|
try {
|
|
10997
11202
|
parsed = JSON.parse(line);
|
|
@@ -11004,6 +11209,9 @@ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
|
11004
11209
|
let access = await fetcher.getAccessToken();
|
|
11005
11210
|
let res = await postFrame(baseUrl, line, access, fetchFn);
|
|
11006
11211
|
if (res.status === 401) {
|
|
11212
|
+
if (isServiceTokenMode) {
|
|
11213
|
+
throw new ServiceToken401Error();
|
|
11214
|
+
}
|
|
11007
11215
|
fetcher.invalidate();
|
|
11008
11216
|
access = await fetcher.getAccessToken();
|
|
11009
11217
|
res = await postFrame(baseUrl, line, access, fetchFn);
|
|
@@ -11222,8 +11430,8 @@ sprint.command("close <sprint-id>").description("close a sprint \u2014 validates
|
|
|
11222
11430
|
}
|
|
11223
11431
|
sprintCloseHandler(handlerOpts);
|
|
11224
11432
|
});
|
|
11225
|
-
sprint.command("reconcile-lifecycle <sprint-id>").description("CR-017: check lifecycle status of artifacts referenced in this sprint's commits (exits 1 on drift)").option("--since <iso-date>", "start of git log range (default: sprint start_date or 90 days ago)").option("--until <iso-date>", "end of git log range (default: now)").action((sprintId, opts) => {
|
|
11226
|
-
reconcileLifecycleCliHandler({ sprintId, since: opts.since, until: opts.until });
|
|
11433
|
+
sprint.command("reconcile-lifecycle <sprint-id>").description("CR-017: check lifecycle status of artifacts referenced in this sprint's commits (exits 1 on drift)").option("--since <iso-date>", "start of git log range (default: sprint start_date or 90 days ago)").option("--until <iso-date>", "end of git log range (default: now)").option("--parents", "audit parent (Epic/Sprint) rollup statuses; read-only (CR-066)").action((sprintId, opts) => {
|
|
11434
|
+
reconcileLifecycleCliHandler({ sprintId, since: opts.since, until: opts.until, parents: opts.parents });
|
|
11227
11435
|
});
|
|
11228
11436
|
sprint.command("archive <sprint-id>").description("archive a completed sprint \u2014 move pending-sync files, clear .active, merge + delete sprint branch").option("--dry-run", "print the archive plan without making any changes").option("--allow-wiki-lint-debt", "CR-022 M5: waive wiki-lint findings during archive (mirrors --allow-drift pattern)").action(async (sprintId, opts) => {
|
|
11229
11437
|
const handlerOpts = { sprintId };
|