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.cjs
CHANGED
|
@@ -423,11 +423,11 @@ function defaultDiskCachePath(env = process.env) {
|
|
|
423
423
|
if (typeof override === "string" && override.length > 0) return override;
|
|
424
424
|
const home = os9.homedir();
|
|
425
425
|
if (!home) return null;
|
|
426
|
-
return
|
|
426
|
+
return path46.join(home, ".cleargate", "access-token.json");
|
|
427
427
|
}
|
|
428
428
|
function readDiskCache(filePath) {
|
|
429
429
|
try {
|
|
430
|
-
const raw =
|
|
430
|
+
const raw = fs43.readFileSync(filePath, "utf8");
|
|
431
431
|
const parsed = JSON.parse(raw);
|
|
432
432
|
if (parsed !== null && typeof parsed === "object" && parsed.version === 1 && typeof parsed.entries === "object" && parsed.entries !== null) {
|
|
433
433
|
return parsed;
|
|
@@ -437,18 +437,18 @@ function readDiskCache(filePath) {
|
|
|
437
437
|
return { version: 1, entries: {} };
|
|
438
438
|
}
|
|
439
439
|
function writeDiskCache(filePath, data) {
|
|
440
|
-
const dir =
|
|
440
|
+
const dir = path46.dirname(filePath);
|
|
441
441
|
try {
|
|
442
|
-
|
|
442
|
+
fs43.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
443
443
|
try {
|
|
444
|
-
|
|
444
|
+
fs43.chmodSync(dir, 448);
|
|
445
445
|
} catch {
|
|
446
446
|
}
|
|
447
|
-
const tmpPath =
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
447
|
+
const tmpPath = path46.join(dir, ".access-token.json.tmp");
|
|
448
|
+
fs43.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
|
|
449
|
+
fs43.chmodSync(tmpPath, 384);
|
|
450
|
+
fs43.renameSync(tmpPath, filePath);
|
|
451
|
+
fs43.chmodSync(filePath, 384);
|
|
452
452
|
} catch {
|
|
453
453
|
}
|
|
454
454
|
}
|
|
@@ -544,14 +544,14 @@ async function acquireAccessToken(opts) {
|
|
|
544
544
|
}
|
|
545
545
|
return accessToken;
|
|
546
546
|
}
|
|
547
|
-
var
|
|
547
|
+
var fs43, os9, path46, CACHE, AcquireError;
|
|
548
548
|
var init_acquire = __esm({
|
|
549
549
|
"src/auth/acquire.ts"() {
|
|
550
550
|
"use strict";
|
|
551
551
|
init_cjs_shims();
|
|
552
|
-
|
|
552
|
+
fs43 = __toESM(require("fs"), 1);
|
|
553
553
|
os9 = __toESM(require("os"), 1);
|
|
554
|
-
|
|
554
|
+
path46 = __toESM(require("path"), 1);
|
|
555
555
|
init_factory();
|
|
556
556
|
CACHE = /* @__PURE__ */ new Map();
|
|
557
557
|
AcquireError = class extends Error {
|
|
@@ -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.13.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.",
|
|
@@ -830,12 +830,11 @@ var package_default = {
|
|
|
830
830
|
build: "tsup",
|
|
831
831
|
dev: "tsup --watch",
|
|
832
832
|
typecheck: "tsc --noEmit",
|
|
833
|
-
test: "tsx --test --test-reporter=spec 'test/**/*.node.test.ts'",
|
|
834
|
-
"test:file": "tsx --test --test-reporter=spec",
|
|
835
|
-
"test:
|
|
836
|
-
"test:
|
|
837
|
-
"
|
|
838
|
-
"test:node:file": "tsx --test --test-reporter=spec"
|
|
833
|
+
test: "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec 'test/**/*.node.test.ts' '!test/fixtures/**'",
|
|
834
|
+
"test:file": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec",
|
|
835
|
+
"test:node": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec 'test/**/*.node.test.ts' '!test/fixtures/**'",
|
|
836
|
+
"test:node:file": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec",
|
|
837
|
+
"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')"`
|
|
839
838
|
},
|
|
840
839
|
dependencies: {
|
|
841
840
|
"@napi-rs/keyring": "^1.2.0",
|
|
@@ -850,10 +849,10 @@ var package_default = {
|
|
|
850
849
|
"@types/js-yaml": "^4.0.9",
|
|
851
850
|
"@types/node": "^24.0.0",
|
|
852
851
|
"@types/pg": "^8.11.10",
|
|
852
|
+
"ts-morph": "28.0.0",
|
|
853
853
|
tsup: "^8",
|
|
854
854
|
tsx: "^4.21.0",
|
|
855
|
-
typescript: "^5.8.0"
|
|
856
|
-
vitest: "^2.1.0"
|
|
855
|
+
typescript: "^5.8.0"
|
|
857
856
|
}
|
|
858
857
|
};
|
|
859
858
|
|
|
@@ -2304,6 +2303,24 @@ var PREFIX_MAP = [
|
|
|
2304
2303
|
{ prefix: "BUG-", type: "bug", bucket: "bugs" },
|
|
2305
2304
|
{ prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
|
|
2306
2305
|
];
|
|
2306
|
+
var SPRINT_REPORT_FILENAMES = ["REPORT.md"];
|
|
2307
|
+
var SPRINT_REPORT_CANONICAL_RE = /^SPRINT-\d{2,}_REPORT\.md$/;
|
|
2308
|
+
function isSprintReportPath(relPath) {
|
|
2309
|
+
const normalised = relPath.replace(/\\/g, "/");
|
|
2310
|
+
const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\/([^/]+)$/.exec(normalised);
|
|
2311
|
+
if (!match) return false;
|
|
2312
|
+
const filename = match[2];
|
|
2313
|
+
return SPRINT_REPORT_FILENAMES.includes(filename) || SPRINT_REPORT_CANONICAL_RE.test(filename);
|
|
2314
|
+
}
|
|
2315
|
+
function deriveBucketFromReportPath(relPath) {
|
|
2316
|
+
const normalised = relPath.replace(/\\/g, "/");
|
|
2317
|
+
const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\//.exec(normalised);
|
|
2318
|
+
if (!match) {
|
|
2319
|
+
throw new Error(`deriveBucketFromReportPath: cannot extract SPRINT-NN from: ${relPath}`);
|
|
2320
|
+
}
|
|
2321
|
+
const id = match[1];
|
|
2322
|
+
return { type: "sprint", id, bucket: "sprints" };
|
|
2323
|
+
}
|
|
2307
2324
|
function deriveBucket(filename) {
|
|
2308
2325
|
const base = filename.includes("/") ? filename.split("/").pop() : filename;
|
|
2309
2326
|
const stem = base.endsWith(".md") ? base.slice(0, -3) : base;
|
|
@@ -2427,6 +2444,12 @@ function serializePage(page, body) {
|
|
|
2427
2444
|
if (page.sprint_cleargate_id !== void 0) {
|
|
2428
2445
|
lines.push(`sprint_cleargate_id: "${page.sprint_cleargate_id}"`);
|
|
2429
2446
|
}
|
|
2447
|
+
if (page.report_raw_path !== void 0) {
|
|
2448
|
+
lines.push(`report_raw_path: "${page.report_raw_path}"`);
|
|
2449
|
+
}
|
|
2450
|
+
if (page.last_report_ingest_commit !== void 0) {
|
|
2451
|
+
lines.push(`last_report_ingest_commit: "${page.last_report_ingest_commit}"`);
|
|
2452
|
+
}
|
|
2430
2453
|
lines.push("---");
|
|
2431
2454
|
const fm = lines.join("\n");
|
|
2432
2455
|
return `${fm}
|
|
@@ -2449,7 +2472,9 @@ function parsePage(raw) {
|
|
|
2449
2472
|
const last_contradict_sha = fm["last_contradict_sha"] !== void 0 ? String(fm["last_contradict_sha"]) : void 0;
|
|
2450
2473
|
const parent_cleargate_id = fm["parent_cleargate_id"] !== void 0 ? String(fm["parent_cleargate_id"]) : void 0;
|
|
2451
2474
|
const sprint_cleargate_id = fm["sprint_cleargate_id"] !== void 0 ? String(fm["sprint_cleargate_id"]) : void 0;
|
|
2452
|
-
|
|
2475
|
+
const report_raw_path = fm["report_raw_path"] !== void 0 ? String(fm["report_raw_path"]) : void 0;
|
|
2476
|
+
const last_report_ingest_commit = fm["last_report_ingest_commit"] !== void 0 ? String(fm["last_report_ingest_commit"]) : void 0;
|
|
2477
|
+
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
2478
|
}
|
|
2454
2479
|
function parseFmRaw(raw) {
|
|
2455
2480
|
const lines = raw.split("\n");
|
|
@@ -3981,28 +4006,33 @@ async function wikiIngestHandler(opts) {
|
|
|
3981
4006
|
const rawPath = opts.rawPath;
|
|
3982
4007
|
const absRawPath = path20.isAbsolute(rawPath) ? rawPath : path20.resolve(cwd, rawPath);
|
|
3983
4008
|
const relRawPath = path20.relative(cwd, absRawPath).replace(/\\/g, "/");
|
|
3984
|
-
const
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4009
|
+
const isSprintReport = isSprintReportPath(relRawPath);
|
|
4010
|
+
if (!isSprintReport) {
|
|
4011
|
+
const deliveryRoot = path20.join(cwd, ".cleargate", "delivery");
|
|
4012
|
+
const absDeliveryRoot = deliveryRoot;
|
|
4013
|
+
const relToDelivery = path20.relative(absDeliveryRoot, absRawPath);
|
|
4014
|
+
if (relToDelivery.startsWith("..") || path20.isAbsolute(relToDelivery)) {
|
|
4015
|
+
stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
|
|
3990
4016
|
`);
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
stdout(`wiki ingest: ${rawPath} excluded (skip)
|
|
4017
|
+
exit(2);
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
4020
|
+
const isExcluded = EXCLUDED_SUFFIXES2.some((excl) => relRawPath.startsWith(excl));
|
|
4021
|
+
if (isExcluded) {
|
|
4022
|
+
stdout(`wiki ingest: ${rawPath} excluded (skip)
|
|
3998
4023
|
`);
|
|
3999
|
-
|
|
4000
|
-
|
|
4024
|
+
exit(0);
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4001
4027
|
}
|
|
4002
4028
|
const filename = path20.basename(absRawPath);
|
|
4003
4029
|
let bucketInfo;
|
|
4004
4030
|
try {
|
|
4005
|
-
|
|
4031
|
+
if (isSprintReport) {
|
|
4032
|
+
bucketInfo = deriveBucketFromReportPath(relRawPath);
|
|
4033
|
+
} else {
|
|
4034
|
+
bucketInfo = deriveBucket(filename);
|
|
4035
|
+
}
|
|
4006
4036
|
} catch (e) {
|
|
4007
4037
|
stderr(`wiki ingest: cannot determine bucket for ${rawPath}: ${e.message}
|
|
4008
4038
|
`);
|
|
@@ -4045,16 +4075,26 @@ async function wikiIngestHandler(opts) {
|
|
|
4045
4075
|
}
|
|
4046
4076
|
const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
|
|
4047
4077
|
const pageExists = fs19.existsSync(pagePath);
|
|
4048
|
-
|
|
4049
|
-
|
|
4078
|
+
let existingPage;
|
|
4079
|
+
if (pageExists) {
|
|
4050
4080
|
try {
|
|
4051
4081
|
const existingPageContent = fs19.readFileSync(pagePath, "utf8");
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4082
|
+
existingPage = parsePage(existingPageContent);
|
|
4083
|
+
} catch {
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
if (existingPage !== void 0 && currentSha !== "") {
|
|
4087
|
+
let isNoOp = false;
|
|
4088
|
+
try {
|
|
4089
|
+
if (isSprintReport) {
|
|
4090
|
+
if (existingPage.last_report_ingest_commit === currentSha) {
|
|
4056
4091
|
isNoOp = true;
|
|
4057
4092
|
}
|
|
4093
|
+
} else {
|
|
4094
|
+
if (existingPage.last_ingest_commit === currentSha) {
|
|
4095
|
+
const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
|
|
4096
|
+
if (contentUnchanged) isNoOp = true;
|
|
4097
|
+
}
|
|
4058
4098
|
}
|
|
4059
4099
|
} catch {
|
|
4060
4100
|
}
|
|
@@ -4066,36 +4106,53 @@ async function wikiIngestHandler(opts) {
|
|
|
4066
4106
|
}
|
|
4067
4107
|
}
|
|
4068
4108
|
const action = pageExists ? "update" : "create";
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
const existingPage = parsePage(existingPageContent);
|
|
4074
|
-
existingLastContradictSha = existingPage.last_contradict_sha;
|
|
4075
|
-
} catch {
|
|
4076
|
-
}
|
|
4077
|
-
}
|
|
4109
|
+
const timestamp = now();
|
|
4110
|
+
const existingLastContradictSha = existingPage?.last_contradict_sha;
|
|
4111
|
+
const existingReportRawPath = existingPage?.report_raw_path;
|
|
4112
|
+
const existingLastReportIngestCommit = existingPage?.last_report_ingest_commit;
|
|
4078
4113
|
const parent = buildParentRef2(fm);
|
|
4079
4114
|
const children = buildChildrenRefs2(fm);
|
|
4080
|
-
const timestamp = now();
|
|
4081
4115
|
const wikiPage = {
|
|
4082
4116
|
type,
|
|
4083
4117
|
id,
|
|
4084
|
-
parent,
|
|
4085
|
-
children,
|
|
4086
|
-
status: String(fm["status"] ?? ""),
|
|
4087
|
-
remote_id: String(fm["remote_id"] ?? ""),
|
|
4088
|
-
raw_path
|
|
4118
|
+
parent: isSprintReport ? existingPage?.parent ?? "" : parent,
|
|
4119
|
+
children: isSprintReport ? existingPage?.children ?? [] : children,
|
|
4120
|
+
status: isSprintReport ? existingPage?.status ?? String(fm["status"] ?? "") : String(fm["status"] ?? ""),
|
|
4121
|
+
remote_id: isSprintReport ? existingPage?.remote_id ?? String(fm["remote_id"] ?? "") : String(fm["remote_id"] ?? ""),
|
|
4122
|
+
// raw_path tracks the plan file path; for report-only ingest preserve existing or use relRawPath as fallback
|
|
4123
|
+
raw_path: isSprintReport ? existingPage?.raw_path ?? relRawPath : relRawPath,
|
|
4089
4124
|
last_ingest: timestamp,
|
|
4090
|
-
last_ingest_commit
|
|
4125
|
+
// last_ingest_commit tracks the plan source; preserve when re-ingesting report
|
|
4126
|
+
last_ingest_commit: isSprintReport ? existingPage?.last_ingest_commit ?? "" : currentSha,
|
|
4091
4127
|
repo,
|
|
4092
4128
|
// Carry forward last_contradict_sha so Phase 4 idempotency survives re-ingest
|
|
4093
4129
|
...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"] } : {}
|
|
4130
|
+
// Hierarchy keys (§11.7): read from raw fm for plan ingest; preserve for report ingest
|
|
4131
|
+
...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"] } : {},
|
|
4132
|
+
...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"] } : {},
|
|
4133
|
+
// CR-063 sprint-report fields
|
|
4134
|
+
report_raw_path: isSprintReport ? relRawPath : existingReportRawPath ?? void 0,
|
|
4135
|
+
last_report_ingest_commit: isSprintReport ? currentSha : existingLastReportIngestCommit ?? void 0
|
|
4097
4136
|
};
|
|
4098
|
-
|
|
4137
|
+
let existingPageBody = "";
|
|
4138
|
+
if (existingPage !== void 0 && pageExists) {
|
|
4139
|
+
try {
|
|
4140
|
+
const existingPageContent = fs19.readFileSync(pagePath, "utf8");
|
|
4141
|
+
const lines = existingPageContent.split("\n");
|
|
4142
|
+
let closingDash = -1;
|
|
4143
|
+
for (let i = 1; i < lines.length; i++) {
|
|
4144
|
+
if (lines[i] === "---") {
|
|
4145
|
+
closingDash = i;
|
|
4146
|
+
break;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
if (closingDash !== -1) {
|
|
4150
|
+
existingPageBody = lines.slice(closingDash + 1).join("\n").replace(/^\n/, "");
|
|
4151
|
+
}
|
|
4152
|
+
} catch {
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
const pageBody = buildPageBody2({ id, fm, body, isSprintReport, existingPageBody });
|
|
4099
4156
|
const pageContent = serializePage(wikiPage, pageBody);
|
|
4100
4157
|
fs19.mkdirSync(pageDir, { recursive: true });
|
|
4101
4158
|
fs19.writeFileSync(pagePath, pageContent, "utf8");
|
|
@@ -4173,7 +4230,37 @@ function buildChildrenRefs2(fm) {
|
|
|
4173
4230
|
return `[[${s}]]`;
|
|
4174
4231
|
});
|
|
4175
4232
|
}
|
|
4233
|
+
function extractReportBlock(pageBody) {
|
|
4234
|
+
const beginMarker = "<!-- BEGIN sprint-report -->";
|
|
4235
|
+
const endMarker = "<!-- END sprint-report -->";
|
|
4236
|
+
const beginIdx = pageBody.indexOf(beginMarker);
|
|
4237
|
+
const endIdx = pageBody.indexOf(endMarker);
|
|
4238
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) return void 0;
|
|
4239
|
+
return pageBody.slice(beginIdx, endIdx + endMarker.length);
|
|
4240
|
+
}
|
|
4241
|
+
function extractPlanStub(pageBody) {
|
|
4242
|
+
const beginMarker = "<!-- BEGIN sprint-report -->";
|
|
4243
|
+
const beginIdx = pageBody.indexOf(beginMarker);
|
|
4244
|
+
if (beginIdx === -1) return pageBody;
|
|
4245
|
+
return pageBody.slice(0, beginIdx);
|
|
4246
|
+
}
|
|
4176
4247
|
function buildPageBody2(item) {
|
|
4248
|
+
const { isSprintReport, existingPageBody } = item;
|
|
4249
|
+
if (isSprintReport) {
|
|
4250
|
+
const planStub = existingPageBody ? extractPlanStub(existingPageBody) : buildPlanStub(item);
|
|
4251
|
+
const reportBlock = buildReportBlock(item.body);
|
|
4252
|
+
return planStub + reportBlock;
|
|
4253
|
+
} else {
|
|
4254
|
+
const planStub = buildPlanStub(item);
|
|
4255
|
+
const existingReportBlock = existingPageBody ? extractReportBlock(existingPageBody) : void 0;
|
|
4256
|
+
if (existingReportBlock !== void 0) {
|
|
4257
|
+
const stub = planStub.trimEnd() + "\n\n";
|
|
4258
|
+
return stub + existingReportBlock + "\n";
|
|
4259
|
+
}
|
|
4260
|
+
return planStub;
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
function buildPlanStub(item) {
|
|
4177
4264
|
const title = String(item.fm["title"] ?? item.id);
|
|
4178
4265
|
const summary = String(
|
|
4179
4266
|
item.fm["description"] ?? item.body.split("\n")[0] ?? "No summary available."
|
|
@@ -4197,6 +4284,16 @@ function buildPageBody2(item) {
|
|
|
4197
4284
|
""
|
|
4198
4285
|
].join("\n");
|
|
4199
4286
|
}
|
|
4287
|
+
function buildReportBlock(reportBody) {
|
|
4288
|
+
return [
|
|
4289
|
+
"<!-- BEGIN sprint-report -->",
|
|
4290
|
+
"## Sprint Report",
|
|
4291
|
+
"",
|
|
4292
|
+
reportBody.trim(),
|
|
4293
|
+
"<!-- END sprint-report -->",
|
|
4294
|
+
""
|
|
4295
|
+
].join("\n");
|
|
4296
|
+
}
|
|
4200
4297
|
function appendLogEntry(wikiRoot, entry) {
|
|
4201
4298
|
const logPath = path20.join(wikiRoot, "log.md");
|
|
4202
4299
|
const logEntry = [
|
|
@@ -4428,9 +4525,9 @@ function detectWorkItemTypeFromFm(fm) {
|
|
|
4428
4525
|
}
|
|
4429
4526
|
function detectWorkItemType(idOrPath) {
|
|
4430
4527
|
const upper = idOrPath.toUpperCase();
|
|
4431
|
-
const
|
|
4528
|
+
const basename14 = upper.split("/").pop() ?? upper;
|
|
4432
4529
|
for (const { prefix, type } of PREFIX_MAP2) {
|
|
4433
|
-
if (
|
|
4530
|
+
if (basename14.includes(prefix)) {
|
|
4434
4531
|
return type;
|
|
4435
4532
|
}
|
|
4436
4533
|
}
|
|
@@ -6677,7 +6774,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
6677
6774
|
throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
|
|
6678
6775
|
}
|
|
6679
6776
|
const existing = coerceCachedGate(fm["cached_gate_result"]);
|
|
6680
|
-
if (existing && JSON.stringify(existing) === JSON.stringify(newResult)) {
|
|
6777
|
+
if (existing && existing.pass === newResult.pass && JSON.stringify(existing.failing_criteria) === JSON.stringify(newResult.failing_criteria)) {
|
|
6681
6778
|
return;
|
|
6682
6779
|
}
|
|
6683
6780
|
const newFm = {};
|
|
@@ -7017,24 +7114,312 @@ function gateRunHandler(name, opts, cli) {
|
|
|
7017
7114
|
|
|
7018
7115
|
// src/commands/sprint.ts
|
|
7019
7116
|
init_cjs_shims();
|
|
7020
|
-
var
|
|
7021
|
-
var
|
|
7117
|
+
var fs35 = __toESM(require("fs"), 1);
|
|
7118
|
+
var path37 = __toESM(require("path"), 1);
|
|
7022
7119
|
var import_node_child_process11 = require("child_process");
|
|
7023
7120
|
var import_js_yaml7 = __toESM(require("js-yaml"), 1);
|
|
7024
7121
|
|
|
7025
7122
|
// src/lib/lifecycle-reconcile.ts
|
|
7026
7123
|
init_cjs_shims();
|
|
7124
|
+
var fs34 = __toESM(require("fs"), 1);
|
|
7125
|
+
var path36 = __toESM(require("path"), 1);
|
|
7126
|
+
var import_node_child_process10 = require("child_process");
|
|
7127
|
+
|
|
7128
|
+
// src/lib/parent-rollup.ts
|
|
7129
|
+
init_cjs_shims();
|
|
7027
7130
|
var fs33 = __toESM(require("fs"), 1);
|
|
7028
7131
|
var path35 = __toESM(require("path"), 1);
|
|
7029
|
-
|
|
7132
|
+
function readFm(filePath) {
|
|
7133
|
+
try {
|
|
7134
|
+
const raw = fs33.readFileSync(filePath, "utf8");
|
|
7135
|
+
const { fm } = parseFrontmatter(raw);
|
|
7136
|
+
return fm;
|
|
7137
|
+
} catch {
|
|
7138
|
+
return null;
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
7141
|
+
function extractId(fm, filePath) {
|
|
7142
|
+
for (const key of [
|
|
7143
|
+
"story_id",
|
|
7144
|
+
"epic_id",
|
|
7145
|
+
"sprint_id",
|
|
7146
|
+
"bug_id",
|
|
7147
|
+
"cr_id",
|
|
7148
|
+
"initiative_id",
|
|
7149
|
+
"hotfix_id"
|
|
7150
|
+
]) {
|
|
7151
|
+
const val = fm[key];
|
|
7152
|
+
if (typeof val === "string" && val.trim() !== "") return val.trim();
|
|
7153
|
+
}
|
|
7154
|
+
const stem = path35.basename(filePath, ".md");
|
|
7155
|
+
return stem.split("_")[0] ?? stem;
|
|
7156
|
+
}
|
|
7157
|
+
function enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache) {
|
|
7158
|
+
const pendingSyncDir = path35.join(deliveryRoot, "pending-sync");
|
|
7159
|
+
const results = [];
|
|
7160
|
+
const pools = [];
|
|
7161
|
+
if (fs33.existsSync(archiveRoot)) pools.push(archiveRoot);
|
|
7162
|
+
if (fs33.existsSync(pendingSyncDir)) pools.push(pendingSyncDir);
|
|
7163
|
+
for (const dir of pools) {
|
|
7164
|
+
let entries;
|
|
7165
|
+
try {
|
|
7166
|
+
entries = fs33.readdirSync(dir);
|
|
7167
|
+
} catch {
|
|
7168
|
+
entries = [];
|
|
7169
|
+
}
|
|
7170
|
+
for (const entry of entries) {
|
|
7171
|
+
if (!entry.endsWith(".md")) continue;
|
|
7172
|
+
const absPath = path35.join(dir, entry);
|
|
7173
|
+
let fm = fmCache.get(absPath);
|
|
7174
|
+
if (fm === void 0) {
|
|
7175
|
+
const parsed = readFm(absPath);
|
|
7176
|
+
if (parsed === null) continue;
|
|
7177
|
+
fm = parsed;
|
|
7178
|
+
fmCache.set(absPath, fm);
|
|
7179
|
+
}
|
|
7180
|
+
const parentCleargateId = fm["parent_cleargate_id"];
|
|
7181
|
+
const parentEpicRef = fm["parent_epic_ref"];
|
|
7182
|
+
const isChild = typeof parentCleargateId === "string" && parentCleargateId.trim() === parentId || typeof parentEpicRef === "string" && parentEpicRef.trim() === parentId;
|
|
7183
|
+
if (!isChild) continue;
|
|
7184
|
+
const childId = extractId(fm, absPath);
|
|
7185
|
+
const status = typeof fm["status"] === "string" ? fm["status"].trim() : "";
|
|
7186
|
+
results.push({ id: childId, status });
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
return results;
|
|
7190
|
+
}
|
|
7191
|
+
async function rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache) {
|
|
7192
|
+
const { deliveryRoot, archiveRoot } = opts;
|
|
7193
|
+
let fm = fmCache.get(parentFilePath);
|
|
7194
|
+
if (fm === void 0) {
|
|
7195
|
+
const raw = readFm(parentFilePath);
|
|
7196
|
+
if (raw === null) {
|
|
7197
|
+
throw new Error(`parent-rollup: cannot read frontmatter from ${parentFilePath}`);
|
|
7198
|
+
}
|
|
7199
|
+
fm = raw;
|
|
7200
|
+
fmCache.set(parentFilePath, fm);
|
|
7201
|
+
}
|
|
7202
|
+
const parentId = extractId(fm, parentFilePath);
|
|
7203
|
+
const currentStatus = typeof fm["status"] === "string" ? fm["status"].trim() : "";
|
|
7204
|
+
if (ARTIFACT_TERMINAL_STATUSES.has(currentStatus)) {
|
|
7205
|
+
return {
|
|
7206
|
+
parent_id: parentId,
|
|
7207
|
+
parent_path: parentFilePath,
|
|
7208
|
+
current_status: currentStatus,
|
|
7209
|
+
proposed_status: null,
|
|
7210
|
+
coverage: "full",
|
|
7211
|
+
terminal_children: [],
|
|
7212
|
+
pending_children: [],
|
|
7213
|
+
verdict: "no-op"
|
|
7214
|
+
};
|
|
7215
|
+
}
|
|
7216
|
+
if (visited.has(parentId)) {
|
|
7217
|
+
throw new Error(`parent-rollup: sub_epics cycle detected at ${parentId}`);
|
|
7218
|
+
}
|
|
7219
|
+
visited.add(parentId);
|
|
7220
|
+
const subEpicsField = fm["sub_epics"];
|
|
7221
|
+
const subEpics = Array.isArray(subEpicsField) && subEpicsField.length > 0 ? subEpicsField.filter((s) => typeof s === "string") : [];
|
|
7222
|
+
if (subEpics.length > 0) {
|
|
7223
|
+
const pendingSyncDir = path35.join(deliveryRoot, "pending-sync");
|
|
7224
|
+
const terminalSubEpics = [];
|
|
7225
|
+
const pendingSubEpics = [];
|
|
7226
|
+
for (const subEpicId of subEpics) {
|
|
7227
|
+
let subEpicPath = null;
|
|
7228
|
+
const candidateDirs = [pendingSyncDir, archiveRoot];
|
|
7229
|
+
for (const dir of candidateDirs) {
|
|
7230
|
+
if (!fs33.existsSync(dir)) continue;
|
|
7231
|
+
let entries;
|
|
7232
|
+
try {
|
|
7233
|
+
entries = fs33.readdirSync(dir);
|
|
7234
|
+
} catch {
|
|
7235
|
+
entries = [];
|
|
7236
|
+
}
|
|
7237
|
+
for (const entry of entries) {
|
|
7238
|
+
if (!entry.endsWith(".md")) continue;
|
|
7239
|
+
const absPath = path35.join(dir, entry);
|
|
7240
|
+
let subFm2 = fmCache.get(absPath);
|
|
7241
|
+
if (subFm2 === void 0) {
|
|
7242
|
+
const parsed = readFm(absPath);
|
|
7243
|
+
if (parsed === null) continue;
|
|
7244
|
+
subFm2 = parsed;
|
|
7245
|
+
fmCache.set(absPath, subFm2);
|
|
7246
|
+
}
|
|
7247
|
+
const entryId = extractId(subFm2, absPath);
|
|
7248
|
+
if (entryId === subEpicId) {
|
|
7249
|
+
subEpicPath = absPath;
|
|
7250
|
+
break;
|
|
7251
|
+
}
|
|
7252
|
+
}
|
|
7253
|
+
if (subEpicPath !== null) break;
|
|
7254
|
+
}
|
|
7255
|
+
if (subEpicPath === null) {
|
|
7256
|
+
pendingSubEpics.push(subEpicId);
|
|
7257
|
+
continue;
|
|
7258
|
+
}
|
|
7259
|
+
let subFm = fmCache.get(subEpicPath);
|
|
7260
|
+
if (subFm === void 0) {
|
|
7261
|
+
const parsed = readFm(subEpicPath);
|
|
7262
|
+
if (parsed === null) {
|
|
7263
|
+
pendingSubEpics.push(subEpicId);
|
|
7264
|
+
continue;
|
|
7265
|
+
}
|
|
7266
|
+
subFm = parsed;
|
|
7267
|
+
fmCache.set(subEpicPath, subFm);
|
|
7268
|
+
}
|
|
7269
|
+
const subStatus = typeof subFm["status"] === "string" ? subFm["status"].trim() : "";
|
|
7270
|
+
if (subStatus === "DEFERRED") {
|
|
7271
|
+
continue;
|
|
7272
|
+
}
|
|
7273
|
+
if (ARTIFACT_TERMINAL_STATUSES.has(subStatus)) {
|
|
7274
|
+
terminalSubEpics.push(subEpicId);
|
|
7275
|
+
continue;
|
|
7276
|
+
}
|
|
7277
|
+
const visitedSnapshot = new Set(visited);
|
|
7278
|
+
const subResult = await rollUpParentStatusInternal(
|
|
7279
|
+
subEpicPath,
|
|
7280
|
+
opts,
|
|
7281
|
+
visitedSnapshot,
|
|
7282
|
+
fmCache
|
|
7283
|
+
);
|
|
7284
|
+
if (subResult.verdict === "auto-flip" || subResult.verdict === "no-op") {
|
|
7285
|
+
terminalSubEpics.push(subEpicId);
|
|
7286
|
+
} else {
|
|
7287
|
+
pendingSubEpics.push(subEpicId);
|
|
7288
|
+
}
|
|
7289
|
+
}
|
|
7290
|
+
visited.delete(parentId);
|
|
7291
|
+
const total2 = terminalSubEpics.length + pendingSubEpics.length;
|
|
7292
|
+
if (total2 === 0) {
|
|
7293
|
+
return {
|
|
7294
|
+
parent_id: parentId,
|
|
7295
|
+
parent_path: parentFilePath,
|
|
7296
|
+
current_status: currentStatus,
|
|
7297
|
+
proposed_status: null,
|
|
7298
|
+
coverage: "zero",
|
|
7299
|
+
terminal_children: [],
|
|
7300
|
+
pending_children: [],
|
|
7301
|
+
verdict: "halt-zero-children",
|
|
7302
|
+
halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
|
|
7303
|
+
};
|
|
7304
|
+
}
|
|
7305
|
+
if (pendingSubEpics.length === 0) {
|
|
7306
|
+
return {
|
|
7307
|
+
parent_id: parentId,
|
|
7308
|
+
parent_path: parentFilePath,
|
|
7309
|
+
current_status: currentStatus,
|
|
7310
|
+
proposed_status: "Completed",
|
|
7311
|
+
coverage: "full",
|
|
7312
|
+
terminal_children: terminalSubEpics,
|
|
7313
|
+
pending_children: [],
|
|
7314
|
+
verdict: "auto-flip"
|
|
7315
|
+
};
|
|
7316
|
+
}
|
|
7317
|
+
return {
|
|
7318
|
+
parent_id: parentId,
|
|
7319
|
+
parent_path: parentFilePath,
|
|
7320
|
+
current_status: currentStatus,
|
|
7321
|
+
proposed_status: null,
|
|
7322
|
+
coverage: "sub-epic-partial",
|
|
7323
|
+
terminal_children: terminalSubEpics,
|
|
7324
|
+
pending_children: pendingSubEpics,
|
|
7325
|
+
verdict: "halt-partial",
|
|
7326
|
+
halt_reason: `${parentId}: ${terminalSubEpics.length}/${total2} sub-epics terminal \u2014 pending: ${pendingSubEpics.join(", ")}`
|
|
7327
|
+
};
|
|
7328
|
+
}
|
|
7329
|
+
const children = enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache);
|
|
7330
|
+
visited.delete(parentId);
|
|
7331
|
+
if (children.length === 0) {
|
|
7332
|
+
return {
|
|
7333
|
+
parent_id: parentId,
|
|
7334
|
+
parent_path: parentFilePath,
|
|
7335
|
+
current_status: currentStatus,
|
|
7336
|
+
proposed_status: null,
|
|
7337
|
+
coverage: "zero",
|
|
7338
|
+
terminal_children: [],
|
|
7339
|
+
pending_children: [],
|
|
7340
|
+
verdict: "halt-zero-children",
|
|
7341
|
+
halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
|
|
7342
|
+
};
|
|
7343
|
+
}
|
|
7344
|
+
const terminalChildren = [];
|
|
7345
|
+
const pendingChildren = [];
|
|
7346
|
+
for (const child of children) {
|
|
7347
|
+
if (ARTIFACT_TERMINAL_STATUSES.has(child.status)) {
|
|
7348
|
+
terminalChildren.push(child.id);
|
|
7349
|
+
} else {
|
|
7350
|
+
pendingChildren.push(child.id);
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
const total = terminalChildren.length + pendingChildren.length;
|
|
7354
|
+
if (pendingChildren.length === 0) {
|
|
7355
|
+
return {
|
|
7356
|
+
parent_id: parentId,
|
|
7357
|
+
parent_path: parentFilePath,
|
|
7358
|
+
current_status: currentStatus,
|
|
7359
|
+
proposed_status: "Completed",
|
|
7360
|
+
coverage: "full",
|
|
7361
|
+
terminal_children: terminalChildren,
|
|
7362
|
+
pending_children: [],
|
|
7363
|
+
verdict: "auto-flip"
|
|
7364
|
+
};
|
|
7365
|
+
}
|
|
7366
|
+
return {
|
|
7367
|
+
parent_id: parentId,
|
|
7368
|
+
parent_path: parentFilePath,
|
|
7369
|
+
current_status: currentStatus,
|
|
7370
|
+
proposed_status: null,
|
|
7371
|
+
coverage: "partial",
|
|
7372
|
+
terminal_children: terminalChildren,
|
|
7373
|
+
pending_children: pendingChildren,
|
|
7374
|
+
verdict: "halt-partial",
|
|
7375
|
+
halt_reason: `${parentId}: ${terminalChildren.length}/${total} children terminal \u2014 pending: ${pendingChildren.join(", ")}`
|
|
7376
|
+
};
|
|
7377
|
+
}
|
|
7378
|
+
async function walkActiveParents(opts) {
|
|
7379
|
+
const { deliveryRoot } = opts;
|
|
7380
|
+
const pendingSyncDir = path35.join(deliveryRoot, "pending-sync");
|
|
7381
|
+
let entries;
|
|
7382
|
+
try {
|
|
7383
|
+
entries = fs33.readdirSync(pendingSyncDir);
|
|
7384
|
+
} catch {
|
|
7385
|
+
return [];
|
|
7386
|
+
}
|
|
7387
|
+
const parentFiles = entries.filter(
|
|
7388
|
+
(e) => e.endsWith(".md") && (e.startsWith("EPIC-") || e.startsWith("SPRINT-"))
|
|
7389
|
+
);
|
|
7390
|
+
const results = [];
|
|
7391
|
+
const fmCache = /* @__PURE__ */ new Map();
|
|
7392
|
+
for (const entry of parentFiles) {
|
|
7393
|
+
const absPath = path35.join(pendingSyncDir, entry);
|
|
7394
|
+
try {
|
|
7395
|
+
const visited = /* @__PURE__ */ new Set();
|
|
7396
|
+
const result = await rollUpParentStatusInternal(absPath, opts, visited, fmCache);
|
|
7397
|
+
results.push(result);
|
|
7398
|
+
} catch (err) {
|
|
7399
|
+
if (err instanceof Error && err.message.includes("sub_epics cycle detected")) {
|
|
7400
|
+
throw err;
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
return results;
|
|
7405
|
+
}
|
|
7406
|
+
|
|
7407
|
+
// src/lib/lifecycle-reconcile.ts
|
|
7408
|
+
var ARTIFACT_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
7409
|
+
"Completed",
|
|
7410
|
+
"Abandoned",
|
|
7411
|
+
"Closed",
|
|
7412
|
+
"Resolved"
|
|
7413
|
+
]);
|
|
7414
|
+
var ARTIFACT_GATE_EXPECTED = ["Completed"];
|
|
7030
7415
|
var VERB_STATUS_MAP = {
|
|
7031
7416
|
feat: {
|
|
7032
7417
|
types: ["STORY", "EPIC", "CR"],
|
|
7033
|
-
expected: [
|
|
7418
|
+
expected: [...ARTIFACT_GATE_EXPECTED]
|
|
7034
7419
|
},
|
|
7035
7420
|
fix: {
|
|
7036
7421
|
types: ["BUG", "HOTFIX"],
|
|
7037
|
-
expected: [
|
|
7422
|
+
expected: [...ARTIFACT_GATE_EXPECTED]
|
|
7038
7423
|
}
|
|
7039
7424
|
};
|
|
7040
7425
|
var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
|
|
@@ -7085,10 +7470,10 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
7085
7470
|
{ rel: "archive", inArchive: true }
|
|
7086
7471
|
];
|
|
7087
7472
|
for (const { rel, inArchive } of dirs) {
|
|
7088
|
-
const dir =
|
|
7473
|
+
const dir = path36.join(deliveryRoot, rel);
|
|
7089
7474
|
let entries;
|
|
7090
7475
|
try {
|
|
7091
|
-
entries =
|
|
7476
|
+
entries = fs34.readdirSync(dir);
|
|
7092
7477
|
} catch {
|
|
7093
7478
|
continue;
|
|
7094
7479
|
}
|
|
@@ -7096,7 +7481,7 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
7096
7481
|
(e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
|
|
7097
7482
|
);
|
|
7098
7483
|
if (match) {
|
|
7099
|
-
const absPath =
|
|
7484
|
+
const absPath = path36.join(dir, match);
|
|
7100
7485
|
return { absPath, inArchive, relPath: `${rel}/${match}` };
|
|
7101
7486
|
}
|
|
7102
7487
|
}
|
|
@@ -7105,7 +7490,7 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
7105
7490
|
function readArtifactStatus(absPath) {
|
|
7106
7491
|
let raw;
|
|
7107
7492
|
try {
|
|
7108
|
-
raw =
|
|
7493
|
+
raw = fs34.readFileSync(absPath, "utf8");
|
|
7109
7494
|
} catch {
|
|
7110
7495
|
return { status: null, carryOver: false };
|
|
7111
7496
|
}
|
|
@@ -7160,7 +7545,7 @@ function reconcileLifecycle(opts) {
|
|
|
7160
7545
|
if (carryOver) continue;
|
|
7161
7546
|
let expectedStatuses;
|
|
7162
7547
|
if (verb === "feat" && type === "BUG") {
|
|
7163
|
-
expectedStatuses = [
|
|
7548
|
+
expectedStatuses = [...ARTIFACT_GATE_EXPECTED];
|
|
7164
7549
|
} else if (!verbConfig.types.includes(type)) {
|
|
7165
7550
|
continue;
|
|
7166
7551
|
} else {
|
|
@@ -7172,7 +7557,7 @@ function reconcileLifecycle(opts) {
|
|
|
7172
7557
|
cleanIds.add(id);
|
|
7173
7558
|
idToItem.delete(id);
|
|
7174
7559
|
} else if (!idToItem.has(id)) {
|
|
7175
|
-
const expectedStr = expectedStatuses[0] ?? "
|
|
7560
|
+
const expectedStr = expectedStatuses[0] ?? "Completed";
|
|
7176
7561
|
idToItem.set(id, {
|
|
7177
7562
|
id,
|
|
7178
7563
|
type,
|
|
@@ -7202,7 +7587,7 @@ function reconcileDecomposition(opts) {
|
|
|
7202
7587
|
const { sprintPlanPath, deliveryRoot } = opts;
|
|
7203
7588
|
let raw;
|
|
7204
7589
|
try {
|
|
7205
|
-
raw =
|
|
7590
|
+
raw = fs34.readFileSync(sprintPlanPath, "utf8");
|
|
7206
7591
|
} catch {
|
|
7207
7592
|
return { missing: [], clean: 0 };
|
|
7208
7593
|
}
|
|
@@ -7214,11 +7599,11 @@ function reconcileDecomposition(opts) {
|
|
|
7214
7599
|
}
|
|
7215
7600
|
const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
|
|
7216
7601
|
const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
|
|
7217
|
-
const pendingDir =
|
|
7218
|
-
const archiveDir =
|
|
7602
|
+
const pendingDir = path36.join(deliveryRoot, "pending-sync");
|
|
7603
|
+
const archiveDir = path36.join(deliveryRoot, "archive");
|
|
7219
7604
|
function listMdFiles(dir) {
|
|
7220
7605
|
try {
|
|
7221
|
-
return
|
|
7606
|
+
return fs34.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
7222
7607
|
} catch {
|
|
7223
7608
|
return [];
|
|
7224
7609
|
}
|
|
@@ -7290,9 +7675,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
|
|
|
7290
7675
|
for (const f of files) {
|
|
7291
7676
|
if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
|
|
7292
7677
|
if (!f.includes(storyPrefix)) continue;
|
|
7293
|
-
const absPath =
|
|
7678
|
+
const absPath = path36.join(dir, f);
|
|
7294
7679
|
try {
|
|
7295
|
-
const raw =
|
|
7680
|
+
const raw = fs34.readFileSync(absPath, "utf8");
|
|
7296
7681
|
const { fm } = parseFrontmatter(raw);
|
|
7297
7682
|
const parentRef = fm["parent_epic_ref"];
|
|
7298
7683
|
if (parentRef === epicId) {
|
|
@@ -7307,9 +7692,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
|
|
|
7307
7692
|
function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
|
|
7308
7693
|
for (const f of pendingFiles) {
|
|
7309
7694
|
if (!f.startsWith("EPIC-")) continue;
|
|
7310
|
-
const absPath =
|
|
7695
|
+
const absPath = path36.join(pendingDir, f);
|
|
7311
7696
|
try {
|
|
7312
|
-
const raw =
|
|
7697
|
+
const raw = fs34.readFileSync(absPath, "utf8");
|
|
7313
7698
|
const { fm } = parseFrontmatter(raw);
|
|
7314
7699
|
const contextSource = fm["context_source"];
|
|
7315
7700
|
if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
|
|
@@ -7335,7 +7720,7 @@ var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandone
|
|
|
7335
7720
|
function resolveRunScript(opts) {
|
|
7336
7721
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
7337
7722
|
const cwd = opts.cwd ?? process.cwd();
|
|
7338
|
-
return
|
|
7723
|
+
return path37.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
7339
7724
|
}
|
|
7340
7725
|
function defaultExit(code) {
|
|
7341
7726
|
return process.exit(code);
|
|
@@ -7354,18 +7739,18 @@ function sprintInitHandler(opts, cli) {
|
|
|
7354
7739
|
if (mode === "v1") {
|
|
7355
7740
|
return printInertAndExit(stdoutFn, exitFn);
|
|
7356
7741
|
}
|
|
7357
|
-
const deliveryRoot =
|
|
7742
|
+
const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
|
|
7358
7743
|
let lifecycleInitMode = "warn";
|
|
7359
7744
|
let sprintPlanPath = null;
|
|
7360
|
-
const pendingDir =
|
|
7745
|
+
const pendingDir = path37.join(deliveryRoot, "pending-sync");
|
|
7361
7746
|
try {
|
|
7362
|
-
const entries =
|
|
7747
|
+
const entries = fs35.readdirSync(pendingDir);
|
|
7363
7748
|
const sprintFile = entries.find(
|
|
7364
7749
|
(e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
|
|
7365
7750
|
);
|
|
7366
7751
|
if (sprintFile) {
|
|
7367
|
-
sprintPlanPath =
|
|
7368
|
-
const raw =
|
|
7752
|
+
sprintPlanPath = path37.join(pendingDir, sprintFile);
|
|
7753
|
+
const raw = fs35.readFileSync(sprintPlanPath, "utf8");
|
|
7369
7754
|
const { fm } = parseFileFrontmatter(raw);
|
|
7370
7755
|
if (fm["lifecycle_init_mode"] === "block") {
|
|
7371
7756
|
lifecycleInitMode = "block";
|
|
@@ -7399,7 +7784,7 @@ function sprintInitHandler(opts, cli) {
|
|
|
7399
7784
|
stderrFn("[cleargate sprint init] lifecycle drift waived via --allow-drift flag");
|
|
7400
7785
|
if (sprintPlanPath) {
|
|
7401
7786
|
try {
|
|
7402
|
-
const rawSprint =
|
|
7787
|
+
const rawSprint = fs35.readFileSync(sprintPlanPath, "utf8");
|
|
7403
7788
|
const { fm, body } = parseFileFrontmatter(rawSprint);
|
|
7404
7789
|
const waiverLine = `lifecycle waiver: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} for ${lifecycleResult.drift.map((d) => d.id).join(", ")}`;
|
|
7405
7790
|
const currentContextSource = typeof fm["context_source"] === "string" ? fm["context_source"] : "";
|
|
@@ -7496,20 +7881,20 @@ function reconcileLifecycleCliHandler(opts, cli) {
|
|
|
7496
7881
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
7497
7882
|
const exitFn = cli?.exit ?? defaultExit;
|
|
7498
7883
|
const cwd = cli?.cwd ?? process.cwd();
|
|
7499
|
-
const deliveryRoot =
|
|
7884
|
+
const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
|
|
7500
7885
|
let since;
|
|
7501
7886
|
let until;
|
|
7502
7887
|
if (opts.since) {
|
|
7503
7888
|
since = new Date(opts.since);
|
|
7504
7889
|
} else {
|
|
7505
7890
|
try {
|
|
7506
|
-
const pendingDir =
|
|
7507
|
-
const entries =
|
|
7891
|
+
const pendingDir = path37.join(deliveryRoot, "pending-sync");
|
|
7892
|
+
const entries = fs35.readdirSync(pendingDir);
|
|
7508
7893
|
const sprintFile = entries.find(
|
|
7509
7894
|
(e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
|
|
7510
7895
|
);
|
|
7511
7896
|
if (sprintFile) {
|
|
7512
|
-
const raw =
|
|
7897
|
+
const raw = fs35.readFileSync(path37.join(pendingDir, sprintFile), "utf8");
|
|
7513
7898
|
const { fm } = parseFileFrontmatter(raw);
|
|
7514
7899
|
const startDate = fm["start_date"];
|
|
7515
7900
|
since = typeof startDate === "string" ? new Date(startDate) : new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
|
|
@@ -7531,22 +7916,50 @@ function reconcileLifecycleCliHandler(opts, cli) {
|
|
|
7531
7916
|
});
|
|
7532
7917
|
if (result.drift.length === 0) {
|
|
7533
7918
|
stdoutFn(`lifecycle: clean (${result.clean} artifacts reconciled)`);
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7919
|
+
} else {
|
|
7920
|
+
stderrFn(`lifecycle: DRIFT detected (${result.drift.length} unreconciled artifacts):`);
|
|
7921
|
+
for (const item of result.drift) {
|
|
7922
|
+
stderrFn(
|
|
7923
|
+
` 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"})`
|
|
7924
|
+
);
|
|
7925
|
+
stderrFn(
|
|
7926
|
+
` Remediation: git mv .cleargate/delivery/pending-sync/${item.file_path?.replace("pending-sync/", "") ?? item.id + "_*.md"} .cleargate/delivery/archive/ && update status: ${item.expected_status}`
|
|
7927
|
+
);
|
|
7928
|
+
}
|
|
7929
|
+
if (!opts.parents) {
|
|
7930
|
+
return exitFn(1);
|
|
7931
|
+
}
|
|
7544
7932
|
}
|
|
7545
|
-
return exitFn(1);
|
|
7546
7933
|
} catch (err) {
|
|
7547
7934
|
stderrFn(`lifecycle reconciliation error: ${err instanceof Error ? err.message : String(err)}`);
|
|
7548
|
-
|
|
7935
|
+
if (!opts.parents) {
|
|
7936
|
+
return exitFn(1);
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
if (opts.parents) {
|
|
7940
|
+
const archiveRoot = path37.join(deliveryRoot, "archive");
|
|
7941
|
+
walkActiveParents({ deliveryRoot, archiveRoot }).then((results) => {
|
|
7942
|
+
stdoutFn("Parent rollup audit (--parents):");
|
|
7943
|
+
for (const r of results) {
|
|
7944
|
+
if (r.verdict === "auto-flip") {
|
|
7945
|
+
stdoutFn(
|
|
7946
|
+
` ${r.parent_id} \u2713 proposed: Completed (${r.terminal_children.length}/${r.terminal_children.length} children Completed)`
|
|
7947
|
+
);
|
|
7948
|
+
} else if (r.verdict === "halt-partial" || r.verdict === "halt-zero-children") {
|
|
7949
|
+
stdoutFn(` ${r.parent_id} \u2717 ${r.verdict}: ${r.halt_reason ?? "no details"}`);
|
|
7950
|
+
} else if (r.verdict === "no-op") {
|
|
7951
|
+
} else {
|
|
7952
|
+
stdoutFn(` ${r.parent_id} ~ ${r.verdict}`);
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
exitFn(0);
|
|
7956
|
+
}).catch((err) => {
|
|
7957
|
+
stderrFn(`--parents audit error: ${err instanceof Error ? err.message : String(err)}`);
|
|
7958
|
+
exitFn(0);
|
|
7959
|
+
});
|
|
7960
|
+
return;
|
|
7549
7961
|
}
|
|
7962
|
+
return exitFn(0);
|
|
7550
7963
|
}
|
|
7551
7964
|
function parseFileFrontmatter(raw) {
|
|
7552
7965
|
const lines = raw.split("\n");
|
|
@@ -7590,8 +8003,8 @@ ${body}`;
|
|
|
7590
8003
|
}
|
|
7591
8004
|
function atomicWriteStr(filePath, content) {
|
|
7592
8005
|
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
7593
|
-
|
|
7594
|
-
|
|
8006
|
+
fs35.writeFileSync(tmp, content, "utf8");
|
|
8007
|
+
fs35.renameSync(tmp, filePath);
|
|
7595
8008
|
}
|
|
7596
8009
|
function deriveSprintBranchForArchive(sprintId) {
|
|
7597
8010
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -7605,7 +8018,7 @@ function stampFile(raw, status, completedAt) {
|
|
|
7605
8018
|
return serializeFileContent(fm, body);
|
|
7606
8019
|
}
|
|
7607
8020
|
function stampSprintClose(sprintPath, now) {
|
|
7608
|
-
const previousContent =
|
|
8021
|
+
const previousContent = fs35.readFileSync(sprintPath, "utf8");
|
|
7609
8022
|
const { fm, body } = parseFileFrontmatter(previousContent);
|
|
7610
8023
|
const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
|
|
7611
8024
|
const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
|
|
@@ -7665,14 +8078,14 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7665
8078
|
if (mode === "v1") {
|
|
7666
8079
|
return printInertAndExit(stdoutFn, exitFn);
|
|
7667
8080
|
}
|
|
7668
|
-
const stateFile =
|
|
7669
|
-
if (!
|
|
8081
|
+
const stateFile = path37.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
|
|
8082
|
+
if (!fs35.existsSync(stateFile)) {
|
|
7670
8083
|
stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
|
|
7671
8084
|
return exitFn(1);
|
|
7672
8085
|
}
|
|
7673
8086
|
let state2;
|
|
7674
8087
|
try {
|
|
7675
|
-
state2 = JSON.parse(
|
|
8088
|
+
state2 = JSON.parse(fs35.readFileSync(stateFile, "utf8"));
|
|
7676
8089
|
} catch (err) {
|
|
7677
8090
|
stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
|
|
7678
8091
|
return exitFn(1);
|
|
@@ -7684,18 +8097,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7684
8097
|
return exitFn(1);
|
|
7685
8098
|
}
|
|
7686
8099
|
const stateStories = state2.stories ?? {};
|
|
7687
|
-
const pendingDir =
|
|
7688
|
-
const archiveDir =
|
|
8100
|
+
const pendingDir = path37.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
8101
|
+
const archiveDir = path37.join(cwd, ".cleargate", "delivery", "archive");
|
|
7689
8102
|
let sprintFile = null;
|
|
7690
|
-
for (const entry of
|
|
8103
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7691
8104
|
if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
|
|
7692
|
-
sprintFile =
|
|
8105
|
+
sprintFile = path37.join(pendingDir, entry);
|
|
7693
8106
|
break;
|
|
7694
8107
|
}
|
|
7695
8108
|
}
|
|
7696
8109
|
let epicIds = [];
|
|
7697
|
-
if (sprintFile &&
|
|
7698
|
-
const { fm } = parseFileFrontmatter(
|
|
8110
|
+
if (sprintFile && fs35.existsSync(sprintFile)) {
|
|
8111
|
+
const { fm } = parseFileFrontmatter(fs35.readFileSync(sprintFile, "utf8"));
|
|
7699
8112
|
const epics = fm["epics"];
|
|
7700
8113
|
if (Array.isArray(epics)) {
|
|
7701
8114
|
epicIds = epics.map(String);
|
|
@@ -7705,15 +8118,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7705
8118
|
if (sprintFile) {
|
|
7706
8119
|
plan.push({
|
|
7707
8120
|
src: sprintFile,
|
|
7708
|
-
destName:
|
|
8121
|
+
destName: path37.basename(sprintFile),
|
|
7709
8122
|
status: "Completed"
|
|
7710
8123
|
});
|
|
7711
8124
|
}
|
|
7712
8125
|
for (const epicId of epicIds) {
|
|
7713
|
-
for (const entry of
|
|
8126
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7714
8127
|
if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
|
|
7715
8128
|
plan.push({
|
|
7716
|
-
src:
|
|
8129
|
+
src: path37.join(pendingDir, entry),
|
|
7717
8130
|
destName: entry,
|
|
7718
8131
|
status: "Approved"
|
|
7719
8132
|
});
|
|
@@ -7722,10 +8135,10 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7722
8135
|
}
|
|
7723
8136
|
const storyKeys = storyKeysForEpic(stateStories, epicId);
|
|
7724
8137
|
for (const storyId of storyKeys) {
|
|
7725
|
-
for (const entry of
|
|
8138
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7726
8139
|
if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
|
|
7727
8140
|
plan.push({
|
|
7728
|
-
src:
|
|
8141
|
+
src: path37.join(pendingDir, entry),
|
|
7729
8142
|
destName: entry,
|
|
7730
8143
|
status: "Done"
|
|
7731
8144
|
});
|
|
@@ -7737,13 +8150,13 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7737
8150
|
const storyIdsInState = new Set(Object.keys(stateStories));
|
|
7738
8151
|
const planSrcs = new Set(plan.map((p) => p.src));
|
|
7739
8152
|
const orphans = [];
|
|
7740
|
-
for (const entry of
|
|
8153
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7741
8154
|
if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
|
|
7742
|
-
const candidate =
|
|
8155
|
+
const candidate = path37.join(pendingDir, entry);
|
|
7743
8156
|
if (planSrcs.has(candidate)) continue;
|
|
7744
8157
|
let raw;
|
|
7745
8158
|
try {
|
|
7746
|
-
raw =
|
|
8159
|
+
raw = fs35.readFileSync(candidate, "utf8");
|
|
7747
8160
|
} catch {
|
|
7748
8161
|
continue;
|
|
7749
8162
|
}
|
|
@@ -7760,18 +8173,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7760
8173
|
}
|
|
7761
8174
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7762
8175
|
const sprintBranch = deriveSprintBranchForArchive(opts.sprintId);
|
|
7763
|
-
const activePath =
|
|
8176
|
+
const activePath = path37.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
7764
8177
|
if (opts.dryRun) {
|
|
7765
8178
|
stdoutFn(`[dry-run] Sprint archive plan for ${opts.sprintId}:`);
|
|
7766
8179
|
stdoutFn(` Sprint branch: ${sprintBranch}`);
|
|
7767
8180
|
stdoutFn(` Files to archive (${plan.length}):`);
|
|
7768
8181
|
for (const entry of plan) {
|
|
7769
8182
|
stdoutFn(
|
|
7770
|
-
` ${
|
|
8183
|
+
` ${path37.basename(entry.src)} \u2192 archive/${entry.destName} [stamp: status=${entry.status}, completed_at=<now>]`
|
|
7771
8184
|
);
|
|
7772
8185
|
}
|
|
7773
8186
|
if (orphans.length > 0) {
|
|
7774
|
-
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) =>
|
|
8187
|
+
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) => path37.basename(o)).join(", ")}`);
|
|
7775
8188
|
}
|
|
7776
8189
|
stdoutFn(` .active \u2192 "" (truncate)`);
|
|
7777
8190
|
stdoutFn(` git checkout main`);
|
|
@@ -7780,9 +8193,9 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7780
8193
|
return exitFn(0);
|
|
7781
8194
|
}
|
|
7782
8195
|
let sprintFileSnapshot = null;
|
|
7783
|
-
const wikiRoot =
|
|
7784
|
-
const wikiInitialised =
|
|
7785
|
-
if (sprintFile &&
|
|
8196
|
+
const wikiRoot = path37.join(cwd, ".cleargate", "wiki");
|
|
8197
|
+
const wikiInitialised = fs35.existsSync(wikiRoot);
|
|
8198
|
+
if (sprintFile && fs35.existsSync(sprintFile)) {
|
|
7786
8199
|
const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
|
|
7787
8200
|
sprintFileSnapshot = previousContent;
|
|
7788
8201
|
if (wikiInitialised) {
|
|
@@ -7809,15 +8222,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7809
8222
|
}
|
|
7810
8223
|
}
|
|
7811
8224
|
for (const entry of plan) {
|
|
7812
|
-
if (!
|
|
8225
|
+
if (!fs35.existsSync(entry.src)) {
|
|
7813
8226
|
stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
|
|
7814
8227
|
continue;
|
|
7815
8228
|
}
|
|
7816
|
-
const raw =
|
|
8229
|
+
const raw = fs35.readFileSync(entry.src, "utf8");
|
|
7817
8230
|
const stamped = stampFile(raw, entry.status, completedAt);
|
|
7818
|
-
const dest =
|
|
8231
|
+
const dest = path37.join(archiveDir, entry.destName);
|
|
7819
8232
|
atomicWriteStr(entry.src, stamped);
|
|
7820
|
-
|
|
8233
|
+
fs35.renameSync(entry.src, dest);
|
|
7821
8234
|
stdoutFn(`archived: ${entry.destName}`);
|
|
7822
8235
|
}
|
|
7823
8236
|
try {
|
|
@@ -7876,14 +8289,14 @@ function checkPrevSprintCompleted(sprintId, cwd) {
|
|
|
7876
8289
|
const prevNum = sprintNum - 1;
|
|
7877
8290
|
const prevId = `SPRINT-${String(prevNum).padStart(2, "0")}`;
|
|
7878
8291
|
const prevIdAlt = `SPRINT-${prevNum}`;
|
|
7879
|
-
const sprintRunsBase =
|
|
8292
|
+
const sprintRunsBase = path37.join(cwd, ".cleargate", "sprint-runs");
|
|
7880
8293
|
let stateJson = null;
|
|
7881
8294
|
let resolvedPrevId = prevId;
|
|
7882
8295
|
for (const pid of [prevId, prevIdAlt]) {
|
|
7883
|
-
const stateFile =
|
|
7884
|
-
if (
|
|
8296
|
+
const stateFile = path37.join(sprintRunsBase, pid, "state.json");
|
|
8297
|
+
if (fs35.existsSync(stateFile)) {
|
|
7885
8298
|
try {
|
|
7886
|
-
const raw =
|
|
8299
|
+
const raw = fs35.readFileSync(stateFile, "utf8");
|
|
7887
8300
|
stateJson = JSON.parse(raw);
|
|
7888
8301
|
resolvedPrevId = pid;
|
|
7889
8302
|
break;
|
|
@@ -7986,21 +8399,21 @@ function checkMainClean(cwd, execFn) {
|
|
|
7986
8399
|
}
|
|
7987
8400
|
function findSprintFile(sprintId, cwd) {
|
|
7988
8401
|
const searchDirs = [
|
|
7989
|
-
|
|
7990
|
-
|
|
8402
|
+
path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
|
|
8403
|
+
path37.join(cwd, ".cleargate", "delivery", "archive")
|
|
7991
8404
|
];
|
|
7992
8405
|
for (const dir of searchDirs) {
|
|
7993
|
-
if (!
|
|
8406
|
+
if (!fs35.existsSync(dir)) continue;
|
|
7994
8407
|
let entries;
|
|
7995
8408
|
try {
|
|
7996
|
-
entries =
|
|
8409
|
+
entries = fs35.readdirSync(dir);
|
|
7997
8410
|
} catch {
|
|
7998
8411
|
continue;
|
|
7999
8412
|
}
|
|
8000
8413
|
const prefix = `${sprintId}_`;
|
|
8001
8414
|
for (const entry of entries) {
|
|
8002
8415
|
if ((entry.startsWith(prefix) || entry === `${sprintId}.md`) && entry.endsWith(".md")) {
|
|
8003
|
-
return
|
|
8416
|
+
return path37.join(dir, entry);
|
|
8004
8417
|
}
|
|
8005
8418
|
}
|
|
8006
8419
|
}
|
|
@@ -8008,26 +8421,26 @@ function findSprintFile(sprintId, cwd) {
|
|
|
8008
8421
|
}
|
|
8009
8422
|
function findWorkItemFileLocal(cwd, workItemId) {
|
|
8010
8423
|
const searchDirs = [
|
|
8011
|
-
|
|
8012
|
-
|
|
8424
|
+
path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
|
|
8425
|
+
path37.join(cwd, ".cleargate", "delivery", "archive")
|
|
8013
8426
|
];
|
|
8014
8427
|
const prefix = `${workItemId}_`;
|
|
8015
8428
|
for (const dir of searchDirs) {
|
|
8016
8429
|
let entries;
|
|
8017
8430
|
try {
|
|
8018
|
-
entries =
|
|
8431
|
+
entries = fs35.readdirSync(dir);
|
|
8019
8432
|
} catch {
|
|
8020
8433
|
continue;
|
|
8021
8434
|
}
|
|
8022
8435
|
const match = entries.find((e) => (e.startsWith(prefix) || e === `${workItemId}.md`) && e.endsWith(".md"));
|
|
8023
|
-
if (match) return
|
|
8436
|
+
if (match) return path37.join(dir, match);
|
|
8024
8437
|
}
|
|
8025
8438
|
return null;
|
|
8026
8439
|
}
|
|
8027
8440
|
function readCachedGateSync(absPath) {
|
|
8028
8441
|
let raw;
|
|
8029
8442
|
try {
|
|
8030
|
-
raw =
|
|
8443
|
+
raw = fs35.readFileSync(absPath, "utf8");
|
|
8031
8444
|
} catch {
|
|
8032
8445
|
return null;
|
|
8033
8446
|
}
|
|
@@ -8051,7 +8464,7 @@ function readCachedGateSync(absPath) {
|
|
|
8051
8464
|
return null;
|
|
8052
8465
|
}
|
|
8053
8466
|
function extractInScopeWorkItemIds(sprintFilePath, cwd, execFn) {
|
|
8054
|
-
const scriptPath =
|
|
8467
|
+
const scriptPath = path37.join(cwd, ".cleargate", "scripts", "assert_story_files.mjs");
|
|
8055
8468
|
const cmd = `node "${scriptPath}" "${sprintFilePath}" --emit-json`;
|
|
8056
8469
|
let stdout;
|
|
8057
8470
|
try {
|
|
@@ -8103,7 +8516,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
|
|
|
8103
8516
|
let fm;
|
|
8104
8517
|
let raw;
|
|
8105
8518
|
try {
|
|
8106
|
-
raw =
|
|
8519
|
+
raw = fs35.readFileSync(absPath, "utf8");
|
|
8107
8520
|
({ fm } = parseFrontmatter(raw));
|
|
8108
8521
|
} catch {
|
|
8109
8522
|
totalChecked++;
|
|
@@ -8147,7 +8560,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
|
|
|
8147
8560
|
let sprintRaw;
|
|
8148
8561
|
let sprintFm = {};
|
|
8149
8562
|
try {
|
|
8150
|
-
sprintRaw =
|
|
8563
|
+
sprintRaw = fs35.readFileSync(sprintFilePath, "utf8");
|
|
8151
8564
|
({ fm: sprintFm } = parseFrontmatter(sprintRaw));
|
|
8152
8565
|
} catch {
|
|
8153
8566
|
}
|
|
@@ -8203,9 +8616,13 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
|
|
|
8203
8616
|
if (!absPath) {
|
|
8204
8617
|
continue;
|
|
8205
8618
|
}
|
|
8619
|
+
if (absPath.includes(`${path37.sep}archive${path37.sep}`)) {
|
|
8620
|
+
result.skipped.push(id);
|
|
8621
|
+
continue;
|
|
8622
|
+
}
|
|
8206
8623
|
let status = "";
|
|
8207
8624
|
try {
|
|
8208
|
-
const raw =
|
|
8625
|
+
const raw = fs35.readFileSync(absPath, "utf8");
|
|
8209
8626
|
const { fm } = parseFrontmatter(raw);
|
|
8210
8627
|
status = String(fm["status"] ?? "");
|
|
8211
8628
|
} catch {
|
|
@@ -8278,8 +8695,8 @@ function sprintPreflightHandler(opts, cli) {
|
|
|
8278
8695
|
|
|
8279
8696
|
// src/commands/story.ts
|
|
8280
8697
|
init_cjs_shims();
|
|
8281
|
-
var
|
|
8282
|
-
var
|
|
8698
|
+
var fs36 = __toESM(require("fs"), 1);
|
|
8699
|
+
var path38 = __toESM(require("path"), 1);
|
|
8283
8700
|
var import_node_child_process12 = require("child_process");
|
|
8284
8701
|
function defaultExit2(code) {
|
|
8285
8702
|
return process.exit(code);
|
|
@@ -8287,7 +8704,7 @@ function defaultExit2(code) {
|
|
|
8287
8704
|
function resolveRunScript2(opts) {
|
|
8288
8705
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
8289
8706
|
const cwd = opts.cwd ?? process.cwd();
|
|
8290
|
-
return
|
|
8707
|
+
return path38.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
8291
8708
|
}
|
|
8292
8709
|
function deriveSprintBranch(sprintId) {
|
|
8293
8710
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -8296,11 +8713,11 @@ function deriveSprintBranch(sprintId) {
|
|
|
8296
8713
|
}
|
|
8297
8714
|
function atomicWriteString(filePath, text) {
|
|
8298
8715
|
const tmpFile = `${filePath}.tmp.${process.pid}`;
|
|
8299
|
-
|
|
8300
|
-
|
|
8716
|
+
fs36.writeFileSync(tmpFile, text, "utf8");
|
|
8717
|
+
fs36.renameSync(tmpFile, filePath);
|
|
8301
8718
|
}
|
|
8302
8719
|
function stateJsonPath(cwd, sprintId) {
|
|
8303
|
-
return
|
|
8720
|
+
return path38.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
|
|
8304
8721
|
}
|
|
8305
8722
|
function storyStartHandler(opts, cli) {
|
|
8306
8723
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -8317,7 +8734,7 @@ function storyStartHandler(opts, cli) {
|
|
|
8317
8734
|
return printInertAndExit(stdoutFn, exitFn);
|
|
8318
8735
|
}
|
|
8319
8736
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
8320
|
-
const worktreePath =
|
|
8737
|
+
const worktreePath = path38.join(cwd, ".worktrees", opts.storyId);
|
|
8321
8738
|
const storyBranch = `story/${opts.storyId}`;
|
|
8322
8739
|
const step1 = spawnFn(
|
|
8323
8740
|
"git",
|
|
@@ -8350,13 +8767,13 @@ function storyStartHandler(opts, cli) {
|
|
|
8350
8767
|
return exitFn(step2.status ?? 1);
|
|
8351
8768
|
}
|
|
8352
8769
|
const stateFile = stateJsonPath(cwd, sprintId);
|
|
8353
|
-
if (!
|
|
8770
|
+
if (!fs36.existsSync(stateFile)) {
|
|
8354
8771
|
stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
|
|
8355
8772
|
return exitFn(1);
|
|
8356
8773
|
}
|
|
8357
8774
|
let state2;
|
|
8358
8775
|
try {
|
|
8359
|
-
state2 = JSON.parse(
|
|
8776
|
+
state2 = JSON.parse(fs36.readFileSync(stateFile, "utf8"));
|
|
8360
8777
|
} catch (err) {
|
|
8361
8778
|
stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
|
|
8362
8779
|
return exitFn(1);
|
|
@@ -8397,7 +8814,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
8397
8814
|
}
|
|
8398
8815
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
8399
8816
|
const storyBranch = `story/${opts.storyId}`;
|
|
8400
|
-
const worktreeRel =
|
|
8817
|
+
const worktreeRel = path38.join(".worktrees", opts.storyId);
|
|
8401
8818
|
const step1 = spawnFn(
|
|
8402
8819
|
"git",
|
|
8403
8820
|
["rev-list", "--count", `${sprintBranch}..${storyBranch}`],
|
|
@@ -8496,7 +8913,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
8496
8913
|
|
|
8497
8914
|
// src/commands/state.ts
|
|
8498
8915
|
init_cjs_shims();
|
|
8499
|
-
var
|
|
8916
|
+
var path39 = __toESM(require("path"), 1);
|
|
8500
8917
|
var import_node_child_process13 = require("child_process");
|
|
8501
8918
|
function defaultExit3(code) {
|
|
8502
8919
|
return process.exit(code);
|
|
@@ -8504,7 +8921,7 @@ function defaultExit3(code) {
|
|
|
8504
8921
|
function resolveRunScript3(opts) {
|
|
8505
8922
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
8506
8923
|
const cwd = opts.cwd ?? process.cwd();
|
|
8507
|
-
return
|
|
8924
|
+
return path39.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
8508
8925
|
}
|
|
8509
8926
|
function stateUpdateHandler(opts, cli) {
|
|
8510
8927
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -8565,21 +8982,21 @@ function stateValidateHandler(opts, cli) {
|
|
|
8565
8982
|
|
|
8566
8983
|
// src/commands/stamp-tokens.ts
|
|
8567
8984
|
init_cjs_shims();
|
|
8568
|
-
var
|
|
8569
|
-
var
|
|
8985
|
+
var fs38 = __toESM(require("fs"), 1);
|
|
8986
|
+
var path41 = __toESM(require("path"), 1);
|
|
8570
8987
|
|
|
8571
8988
|
// src/lib/ledger-reader.ts
|
|
8572
8989
|
init_cjs_shims();
|
|
8573
|
-
var
|
|
8574
|
-
var
|
|
8990
|
+
var fs37 = __toESM(require("fs"), 1);
|
|
8991
|
+
var path40 = __toESM(require("path"), 1);
|
|
8575
8992
|
function findSprintRunsRoot(startDir) {
|
|
8576
8993
|
let dir = startDir;
|
|
8577
8994
|
while (true) {
|
|
8578
|
-
const candidate =
|
|
8579
|
-
if (
|
|
8995
|
+
const candidate = path40.join(dir, ".cleargate", "sprint-runs");
|
|
8996
|
+
if (fs37.existsSync(candidate)) {
|
|
8580
8997
|
return candidate;
|
|
8581
8998
|
}
|
|
8582
|
-
const parent =
|
|
8999
|
+
const parent = path40.dirname(dir);
|
|
8583
9000
|
if (parent === dir) {
|
|
8584
9001
|
return null;
|
|
8585
9002
|
}
|
|
@@ -8632,13 +9049,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
8632
9049
|
}
|
|
8633
9050
|
sprintRunsRoot = found;
|
|
8634
9051
|
}
|
|
8635
|
-
if (!
|
|
9052
|
+
if (!fs37.existsSync(sprintRunsRoot)) {
|
|
8636
9053
|
return [];
|
|
8637
9054
|
}
|
|
8638
9055
|
let ledgerFiles;
|
|
8639
9056
|
try {
|
|
8640
|
-
const entries =
|
|
8641
|
-
ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
9057
|
+
const entries = fs37.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
9058
|
+
ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path40.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs37.existsSync(f));
|
|
8642
9059
|
} catch {
|
|
8643
9060
|
return [];
|
|
8644
9061
|
}
|
|
@@ -8646,7 +9063,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
8646
9063
|
for (const ledgerFile of ledgerFiles) {
|
|
8647
9064
|
let content;
|
|
8648
9065
|
try {
|
|
8649
|
-
content =
|
|
9066
|
+
content = fs37.readFileSync(ledgerFile, "utf-8");
|
|
8650
9067
|
} catch {
|
|
8651
9068
|
continue;
|
|
8652
9069
|
}
|
|
@@ -8697,7 +9114,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
8697
9114
|
});
|
|
8698
9115
|
const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
|
|
8699
9116
|
const cwd = cli?.cwd ?? process.cwd();
|
|
8700
|
-
const absPath =
|
|
9117
|
+
const absPath = path41.isAbsolute(file) ? file : path41.resolve(cwd, file);
|
|
8701
9118
|
if (/\/\.cleargate\/delivery\/archive\//.test(absPath)) {
|
|
8702
9119
|
stdoutFn(`[frozen] ${absPath}`);
|
|
8703
9120
|
exitFn(0);
|
|
@@ -8705,7 +9122,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
8705
9122
|
}
|
|
8706
9123
|
let rawContent;
|
|
8707
9124
|
try {
|
|
8708
|
-
rawContent =
|
|
9125
|
+
rawContent = fs38.readFileSync(absPath, "utf-8");
|
|
8709
9126
|
} catch {
|
|
8710
9127
|
stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
|
|
8711
9128
|
exitFn(1);
|
|
@@ -8777,7 +9194,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
8777
9194
|
return;
|
|
8778
9195
|
}
|
|
8779
9196
|
try {
|
|
8780
|
-
|
|
9197
|
+
fs38.writeFileSync(absPath, serialized, "utf-8");
|
|
8781
9198
|
} catch {
|
|
8782
9199
|
stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
|
|
8783
9200
|
exitFn(1);
|
|
@@ -8794,14 +9211,14 @@ function extractWorkItemId(fm, absPath) {
|
|
|
8794
9211
|
return val.trim();
|
|
8795
9212
|
}
|
|
8796
9213
|
}
|
|
8797
|
-
const
|
|
8798
|
-
const match =
|
|
9214
|
+
const basename14 = path41.basename(absPath);
|
|
9215
|
+
const match = basename14.match(/^(STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(-\d+)?/i);
|
|
8799
9216
|
if (match) {
|
|
8800
9217
|
return match[0].toUpperCase();
|
|
8801
9218
|
}
|
|
8802
9219
|
const typeFromPath = detectWorkItemType(absPath);
|
|
8803
9220
|
if (typeFromPath) {
|
|
8804
|
-
const idMatch =
|
|
9221
|
+
const idMatch = basename14.match(/((?:STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(?:-\d+)?)/i);
|
|
8805
9222
|
if (idMatch) {
|
|
8806
9223
|
return idMatch[1].toUpperCase();
|
|
8807
9224
|
}
|
|
@@ -8899,9 +9316,9 @@ ${body}`;
|
|
|
8899
9316
|
|
|
8900
9317
|
// src/commands/upgrade.ts
|
|
8901
9318
|
init_cjs_shims();
|
|
8902
|
-
var
|
|
9319
|
+
var fs39 = __toESM(require("fs"), 1);
|
|
8903
9320
|
var fsp = __toESM(require("fs/promises"), 1);
|
|
8904
|
-
var
|
|
9321
|
+
var path42 = __toESM(require("path"), 1);
|
|
8905
9322
|
|
|
8906
9323
|
// src/lib/changelog.ts
|
|
8907
9324
|
init_cjs_shims();
|
|
@@ -9105,7 +9522,7 @@ async function writeAtomic2(filePath, content) {
|
|
|
9105
9522
|
}
|
|
9106
9523
|
}
|
|
9107
9524
|
async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
9108
|
-
const snapshotPath =
|
|
9525
|
+
const snapshotPath = path42.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
9109
9526
|
let snapshot;
|
|
9110
9527
|
try {
|
|
9111
9528
|
const raw = await fsp.readFile(snapshotPath, "utf-8");
|
|
@@ -9122,17 +9539,17 @@ async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
|
9122
9539
|
await writeAtomic2(snapshotPath, JSON.stringify(updated, null, 2) + "\n");
|
|
9123
9540
|
}
|
|
9124
9541
|
function isClaudeMd(filePath) {
|
|
9125
|
-
return
|
|
9542
|
+
return path42.basename(filePath) === "CLAUDE.md";
|
|
9126
9543
|
}
|
|
9127
9544
|
function isSettingsJson(filePath) {
|
|
9128
|
-
return
|
|
9545
|
+
return path42.basename(filePath) === "settings.json" && filePath.includes(".claude");
|
|
9129
9546
|
}
|
|
9130
9547
|
async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
9131
|
-
const targetPath =
|
|
9132
|
-
const sourcePath =
|
|
9548
|
+
const targetPath = path42.join(projectRoot, entry.path);
|
|
9549
|
+
const sourcePath = path42.join(packageRoot, entry.path);
|
|
9133
9550
|
try {
|
|
9134
9551
|
const pkgContent = await fsp.readFile(sourcePath, "utf-8");
|
|
9135
|
-
await fsp.mkdir(
|
|
9552
|
+
await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
|
|
9136
9553
|
await writeAtomic2(targetPath, pkgContent);
|
|
9137
9554
|
await updateSnapshotEntry(projectRoot, entry.path, entry.sha256);
|
|
9138
9555
|
stdout(`[always] overwritten: ${entry.path}`);
|
|
@@ -9142,8 +9559,8 @@ async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
|
9142
9559
|
}
|
|
9143
9560
|
async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, currentSha, flags, opts) {
|
|
9144
9561
|
const { stdout, stderr, promptMergeChoiceFn, openInEditorFn, stdin } = opts;
|
|
9145
|
-
const targetPath =
|
|
9146
|
-
const sourcePath =
|
|
9562
|
+
const targetPath = path42.join(projectRoot, entry.path);
|
|
9563
|
+
const sourcePath = path42.join(packageRoot, entry.path);
|
|
9147
9564
|
let ours = "";
|
|
9148
9565
|
let theirs = "";
|
|
9149
9566
|
try {
|
|
@@ -9206,7 +9623,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
9206
9623
|
mergedContent = theirs;
|
|
9207
9624
|
}
|
|
9208
9625
|
}
|
|
9209
|
-
await fsp.mkdir(
|
|
9626
|
+
await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
|
|
9210
9627
|
await writeAtomic2(targetPath, mergedContent);
|
|
9211
9628
|
const newSha2 = hashNormalized(mergedContent);
|
|
9212
9629
|
await updateSnapshotEntry(projectRoot, entry.path, newSha2);
|
|
@@ -9218,7 +9635,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
9218
9635
|
${ours}=======
|
|
9219
9636
|
${theirs}>>>>>>> theirs (upstream)
|
|
9220
9637
|
`;
|
|
9221
|
-
await fsp.mkdir(
|
|
9638
|
+
await fsp.mkdir(path42.dirname(mergeFilePath), { recursive: true });
|
|
9222
9639
|
await writeAtomic2(mergeFilePath, conflictContent);
|
|
9223
9640
|
try {
|
|
9224
9641
|
const result = await openInEditorFn(mergeFilePath);
|
|
@@ -9278,10 +9695,10 @@ async function upgradeHandler(flags, cli) {
|
|
|
9278
9695
|
const installedVersion = installSnapshot?.cleargate_version ?? pkgManifest.cleargate_version;
|
|
9279
9696
|
const targetVersion = pkgManifest.cleargate_version;
|
|
9280
9697
|
if (installedVersion !== targetVersion) {
|
|
9281
|
-
const pkgRoot = cli?.packageRoot ??
|
|
9282
|
-
const changelogPath =
|
|
9698
|
+
const pkgRoot = cli?.packageRoot ?? path42.join(path42.dirname(new URL(importMetaUrl).pathname), "..", "..");
|
|
9699
|
+
const changelogPath = path42.join(pkgRoot, "CHANGELOG.md");
|
|
9283
9700
|
try {
|
|
9284
|
-
const changelogContent =
|
|
9701
|
+
const changelogContent = fs39.readFileSync(changelogPath, "utf-8");
|
|
9285
9702
|
const sections = sliceChangelog(changelogContent, installedVersion, targetVersion);
|
|
9286
9703
|
if (sections.length > 0) {
|
|
9287
9704
|
const deltaText = sections.map((s) => s.body).join("\n\n");
|
|
@@ -9347,7 +9764,7 @@ async function upgradeHandler(flags, cli) {
|
|
|
9347
9764
|
const { entry, currentSha, installSha, action } = item;
|
|
9348
9765
|
let preMutationContent = null;
|
|
9349
9766
|
if (SESSION_LOAD_PATHS.has(entry.path)) {
|
|
9350
|
-
const targetPath =
|
|
9767
|
+
const targetPath = path42.join(cwd, entry.path);
|
|
9351
9768
|
try {
|
|
9352
9769
|
preMutationContent = await fsp.readFile(targetPath, "utf-8");
|
|
9353
9770
|
} catch {
|
|
@@ -9385,7 +9802,7 @@ async function upgradeHandler(flags, cli) {
|
|
|
9385
9802
|
package_sha: entry.sha256
|
|
9386
9803
|
};
|
|
9387
9804
|
if (SESSION_LOAD_PATHS.has(entry.path) && preMutationContent !== null) {
|
|
9388
|
-
const targetPath =
|
|
9805
|
+
const targetPath = path42.join(cwd, entry.path);
|
|
9389
9806
|
let postMutationContent;
|
|
9390
9807
|
try {
|
|
9391
9808
|
postMutationContent = await fsp.readFile(targetPath, "utf-8");
|
|
@@ -9410,9 +9827,9 @@ async function upgradeHandler(flags, cli) {
|
|
|
9410
9827
|
|
|
9411
9828
|
// src/commands/uninstall.ts
|
|
9412
9829
|
init_cjs_shims();
|
|
9413
|
-
var
|
|
9830
|
+
var fs40 = __toESM(require("fs"), 1);
|
|
9414
9831
|
var fsp2 = __toESM(require("fs/promises"), 1);
|
|
9415
|
-
var
|
|
9832
|
+
var path43 = __toESM(require("path"), 1);
|
|
9416
9833
|
var import_node_child_process15 = require("child_process");
|
|
9417
9834
|
var USER_ARTIFACT_TIERS = ["user-artifact"];
|
|
9418
9835
|
var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
|
|
@@ -9438,10 +9855,10 @@ function shouldPreserve(entry, preserveSet, removeSet) {
|
|
|
9438
9855
|
return false;
|
|
9439
9856
|
}
|
|
9440
9857
|
function resolveProjectName(target) {
|
|
9441
|
-
const pkgPath =
|
|
9442
|
-
if (
|
|
9858
|
+
const pkgPath = path43.join(target, "package.json");
|
|
9859
|
+
if (fs40.existsSync(pkgPath)) {
|
|
9443
9860
|
try {
|
|
9444
|
-
const raw =
|
|
9861
|
+
const raw = fs40.readFileSync(pkgPath, "utf-8");
|
|
9445
9862
|
const parsed = JSON.parse(raw);
|
|
9446
9863
|
if (parsed.name && typeof parsed.name === "string") {
|
|
9447
9864
|
return parsed.name;
|
|
@@ -9449,7 +9866,7 @@ function resolveProjectName(target) {
|
|
|
9449
9866
|
} catch {
|
|
9450
9867
|
}
|
|
9451
9868
|
}
|
|
9452
|
-
return
|
|
9869
|
+
return path43.basename(target);
|
|
9453
9870
|
}
|
|
9454
9871
|
function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
9455
9872
|
const run = gitRunner ?? ((args) => {
|
|
@@ -9478,8 +9895,8 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
|
9478
9895
|
return changedFiles.filter((f) => manifestSet.has(f));
|
|
9479
9896
|
}
|
|
9480
9897
|
async function removeFromPackageJson(target, dryRun) {
|
|
9481
|
-
const pkgPath =
|
|
9482
|
-
if (!
|
|
9898
|
+
const pkgPath = path43.join(target, "package.json");
|
|
9899
|
+
if (!fs40.existsSync(pkgPath)) return false;
|
|
9483
9900
|
let raw;
|
|
9484
9901
|
try {
|
|
9485
9902
|
raw = await fsp2.readFile(pkgPath, "utf-8");
|
|
@@ -9520,7 +9937,7 @@ async function removeFile(filePath) {
|
|
|
9520
9937
|
}
|
|
9521
9938
|
async function removeDir(dirPath) {
|
|
9522
9939
|
try {
|
|
9523
|
-
|
|
9940
|
+
fs40.rmSync(dirPath, { recursive: true, force: true });
|
|
9524
9941
|
} catch {
|
|
9525
9942
|
}
|
|
9526
9943
|
}
|
|
@@ -9540,12 +9957,12 @@ async function uninstallHandler(opts) {
|
|
|
9540
9957
|
for (const t of FRAMEWORK_TIERS) removeSet.add(t);
|
|
9541
9958
|
for (const u of USER_ARTIFACT_TIERS) removeSet.add(u);
|
|
9542
9959
|
}
|
|
9543
|
-
const target = opts.path ?
|
|
9544
|
-
const cleargateDir =
|
|
9545
|
-
const manifestPath =
|
|
9546
|
-
const uninstalledPath =
|
|
9547
|
-
if (!
|
|
9548
|
-
if (
|
|
9960
|
+
const target = opts.path ? path43.resolve(opts.path) : cwd;
|
|
9961
|
+
const cleargateDir = path43.join(target, ".cleargate");
|
|
9962
|
+
const manifestPath = path43.join(cleargateDir, ".install-manifest.json");
|
|
9963
|
+
const uninstalledPath = path43.join(cleargateDir, ".uninstalled");
|
|
9964
|
+
if (!fs40.existsSync(manifestPath)) {
|
|
9965
|
+
if (fs40.existsSync(uninstalledPath)) {
|
|
9549
9966
|
stdout("already uninstalled");
|
|
9550
9967
|
exit(0);
|
|
9551
9968
|
return;
|
|
@@ -9554,7 +9971,7 @@ async function uninstallHandler(opts) {
|
|
|
9554
9971
|
exit(0);
|
|
9555
9972
|
return;
|
|
9556
9973
|
}
|
|
9557
|
-
if (
|
|
9974
|
+
if (fs40.existsSync(uninstalledPath) && !fs40.existsSync(manifestPath)) {
|
|
9558
9975
|
stdout("already uninstalled");
|
|
9559
9976
|
exit(0);
|
|
9560
9977
|
return;
|
|
@@ -9576,10 +9993,10 @@ async function uninstallHandler(opts) {
|
|
|
9576
9993
|
return;
|
|
9577
9994
|
}
|
|
9578
9995
|
}
|
|
9579
|
-
const claudeMdPath =
|
|
9996
|
+
const claudeMdPath = path43.join(target, "CLAUDE.md");
|
|
9580
9997
|
let claudeMdContent = null;
|
|
9581
|
-
if (
|
|
9582
|
-
claudeMdContent =
|
|
9998
|
+
if (fs40.existsSync(claudeMdPath)) {
|
|
9999
|
+
claudeMdContent = fs40.readFileSync(claudeMdPath, "utf-8");
|
|
9583
10000
|
if (!claudeMdContent.includes(CLEARGATE_START)) {
|
|
9584
10001
|
stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
|
|
9585
10002
|
exit(1);
|
|
@@ -9595,8 +10012,8 @@ async function uninstallHandler(opts) {
|
|
|
9595
10012
|
const toPreserve = [];
|
|
9596
10013
|
const toSkip = [];
|
|
9597
10014
|
for (const entry of snapshot.files) {
|
|
9598
|
-
const filePath =
|
|
9599
|
-
if (!
|
|
10015
|
+
const filePath = path43.join(target, entry.path);
|
|
10016
|
+
if (!fs40.existsSync(filePath)) {
|
|
9600
10017
|
toSkip.push(entry);
|
|
9601
10018
|
continue;
|
|
9602
10019
|
}
|
|
@@ -9653,7 +10070,7 @@ async function uninstallHandler(opts) {
|
|
|
9653
10070
|
const removedPaths = [];
|
|
9654
10071
|
const preservedPaths = [];
|
|
9655
10072
|
for (const entry of toRemove) {
|
|
9656
|
-
const filePath =
|
|
10073
|
+
const filePath = path43.join(target, entry.path);
|
|
9657
10074
|
await removeFile(filePath);
|
|
9658
10075
|
removedPaths.push(entry.path);
|
|
9659
10076
|
}
|
|
@@ -9669,10 +10086,10 @@ async function uninstallHandler(opts) {
|
|
|
9669
10086
|
stderr(`Warning: could not strip CLAUDE.md block: ${err.message}`);
|
|
9670
10087
|
}
|
|
9671
10088
|
}
|
|
9672
|
-
const settingsPath =
|
|
9673
|
-
if (
|
|
10089
|
+
const settingsPath = path43.join(target, ".claude", "settings.json");
|
|
10090
|
+
if (fs40.existsSync(settingsPath)) {
|
|
9674
10091
|
try {
|
|
9675
|
-
const raw =
|
|
10092
|
+
const raw = fs40.readFileSync(settingsPath, "utf-8");
|
|
9676
10093
|
const settings = JSON.parse(raw);
|
|
9677
10094
|
const cleaned = removeClearGateHooks(settings);
|
|
9678
10095
|
await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
|
|
@@ -9687,7 +10104,7 @@ async function uninstallHandler(opts) {
|
|
|
9687
10104
|
stdout("Removed @cleargate/cli from package.json. Run `npm install` to update package-lock.json.");
|
|
9688
10105
|
}
|
|
9689
10106
|
await removeFile(manifestPath);
|
|
9690
|
-
await removeFile(
|
|
10107
|
+
await removeFile(path43.join(cleargateDir, ".drift-state.json"));
|
|
9691
10108
|
const marker = {
|
|
9692
10109
|
uninstalled_at: now().toISOString(),
|
|
9693
10110
|
prior_version: snapshot.cleargate_version,
|
|
@@ -9712,30 +10129,30 @@ async function uninstallHandler(opts) {
|
|
|
9712
10129
|
// src/commands/sync.ts
|
|
9713
10130
|
init_cjs_shims();
|
|
9714
10131
|
var fsPromises8 = __toESM(require("fs/promises"), 1);
|
|
9715
|
-
var
|
|
10132
|
+
var path52 = __toESM(require("path"), 1);
|
|
9716
10133
|
|
|
9717
10134
|
// src/lib/sync-log.ts
|
|
9718
10135
|
init_cjs_shims();
|
|
9719
|
-
var
|
|
10136
|
+
var fs41 = __toESM(require("fs"), 1);
|
|
9720
10137
|
var fsPromises2 = __toESM(require("fs/promises"), 1);
|
|
9721
|
-
var
|
|
10138
|
+
var path44 = __toESM(require("path"), 1);
|
|
9722
10139
|
function resolveActiveSprintDir(projectRoot, _opts) {
|
|
9723
|
-
const sprintRunsRoot =
|
|
9724
|
-
const offSprint =
|
|
9725
|
-
if (!
|
|
9726
|
-
|
|
9727
|
-
|
|
10140
|
+
const sprintRunsRoot = path44.join(projectRoot, ".cleargate", "sprint-runs");
|
|
10141
|
+
const offSprint = path44.join(sprintRunsRoot, "_off-sprint");
|
|
10142
|
+
if (!fs41.existsSync(sprintRunsRoot)) {
|
|
10143
|
+
fs41.mkdirSync(sprintRunsRoot, { recursive: true });
|
|
10144
|
+
fs41.mkdirSync(offSprint, { recursive: true });
|
|
9728
10145
|
return offSprint;
|
|
9729
10146
|
}
|
|
9730
|
-
const entries =
|
|
10147
|
+
const entries = fs41.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
9731
10148
|
const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
|
|
9732
|
-
const fullPath =
|
|
9733
|
-
const stat =
|
|
10149
|
+
const fullPath = path44.join(sprintRunsRoot, e.name);
|
|
10150
|
+
const stat = fs41.statSync(fullPath);
|
|
9734
10151
|
return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
|
|
9735
10152
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
9736
10153
|
if (sprintDirs.length === 0) {
|
|
9737
|
-
if (!
|
|
9738
|
-
|
|
10154
|
+
if (!fs41.existsSync(offSprint)) {
|
|
10155
|
+
fs41.mkdirSync(offSprint, { recursive: true });
|
|
9739
10156
|
}
|
|
9740
10157
|
return offSprint;
|
|
9741
10158
|
}
|
|
@@ -9746,7 +10163,7 @@ function redactDetail(detail) {
|
|
|
9746
10163
|
return detail.replace(/eyJ[A-Za-z0-9._-]+/g, "[REDACTED]");
|
|
9747
10164
|
}
|
|
9748
10165
|
async function appendSyncLog(sprintRoot, entry) {
|
|
9749
|
-
const logPath =
|
|
10166
|
+
const logPath = path44.join(sprintRoot, "sync-log.jsonl");
|
|
9750
10167
|
await fsPromises2.mkdir(sprintRoot, { recursive: true });
|
|
9751
10168
|
const safeEntry = {
|
|
9752
10169
|
...entry,
|
|
@@ -9756,7 +10173,7 @@ async function appendSyncLog(sprintRoot, entry) {
|
|
|
9756
10173
|
await fsPromises2.appendFile(logPath, line, { encoding: "utf8" });
|
|
9757
10174
|
}
|
|
9758
10175
|
async function readSyncLog(sprintRoot, filters) {
|
|
9759
|
-
const logPath =
|
|
10176
|
+
const logPath = path44.join(sprintRoot, "sync-log.jsonl");
|
|
9760
10177
|
let raw;
|
|
9761
10178
|
try {
|
|
9762
10179
|
raw = await fsPromises2.readFile(logPath, "utf8");
|
|
@@ -9862,7 +10279,7 @@ function classify2(local, remote, since) {
|
|
|
9862
10279
|
init_cjs_shims();
|
|
9863
10280
|
var import_node_fs2 = require("fs");
|
|
9864
10281
|
var os8 = __toESM(require("os"), 1);
|
|
9865
|
-
var
|
|
10282
|
+
var path45 = __toESM(require("path"), 1);
|
|
9866
10283
|
function promptFourChoice(opts) {
|
|
9867
10284
|
const { stdin, stdout } = opts;
|
|
9868
10285
|
stdout("[k]eep mine / [t]ake theirs / [e]dit in $EDITOR / [a]bort: ");
|
|
@@ -9932,7 +10349,7 @@ async function promptThreeWayMerge(opts) {
|
|
|
9932
10349
|
case "a":
|
|
9933
10350
|
return { resolution: "aborted", body: local };
|
|
9934
10351
|
case "e": {
|
|
9935
|
-
const tmpFile =
|
|
10352
|
+
const tmpFile = path45.join(os8.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
|
|
9936
10353
|
const markerContent = `<<<<<<< local
|
|
9937
10354
|
${local}
|
|
9938
10355
|
=======
|
|
@@ -10032,12 +10449,12 @@ init_config();
|
|
|
10032
10449
|
// src/lib/intake.ts
|
|
10033
10450
|
init_cjs_shims();
|
|
10034
10451
|
var fsPromises4 = __toESM(require("fs/promises"), 1);
|
|
10035
|
-
var
|
|
10452
|
+
var path48 = __toESM(require("path"), 1);
|
|
10036
10453
|
|
|
10037
10454
|
// src/lib/slug.ts
|
|
10038
10455
|
init_cjs_shims();
|
|
10039
10456
|
var fsPromises3 = __toESM(require("fs/promises"), 1);
|
|
10040
|
-
var
|
|
10457
|
+
var path47 = __toESM(require("path"), 1);
|
|
10041
10458
|
function slugify(title, max = 40) {
|
|
10042
10459
|
const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
10043
10460
|
const lowered = normalized.toLowerCase();
|
|
@@ -10052,8 +10469,8 @@ function slugify(title, max = 40) {
|
|
|
10052
10469
|
var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
|
|
10053
10470
|
async function nextProposalId(projectRoot) {
|
|
10054
10471
|
const dirs = [
|
|
10055
|
-
|
|
10056
|
-
|
|
10472
|
+
path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
10473
|
+
path47.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
10057
10474
|
];
|
|
10058
10475
|
let maxN = 0;
|
|
10059
10476
|
for (const dir of dirs) {
|
|
@@ -10065,7 +10482,7 @@ async function nextProposalId(projectRoot) {
|
|
|
10065
10482
|
}
|
|
10066
10483
|
for (const entry of entries) {
|
|
10067
10484
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10068
|
-
const fullPath =
|
|
10485
|
+
const fullPath = path47.join(dir, entry.name);
|
|
10069
10486
|
try {
|
|
10070
10487
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
10071
10488
|
const fmEnd = extractFrontmatterBlock(raw);
|
|
@@ -10083,8 +10500,8 @@ async function nextProposalId(projectRoot) {
|
|
|
10083
10500
|
}
|
|
10084
10501
|
async function findByRemoteId(projectRoot, remoteId) {
|
|
10085
10502
|
const dirs = [
|
|
10086
|
-
|
|
10087
|
-
|
|
10503
|
+
path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
10504
|
+
path47.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
10088
10505
|
];
|
|
10089
10506
|
const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10090
10507
|
const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
|
|
@@ -10097,7 +10514,7 @@ async function findByRemoteId(projectRoot, remoteId) {
|
|
|
10097
10514
|
}
|
|
10098
10515
|
for (const entry of entries) {
|
|
10099
10516
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10100
|
-
const fullPath =
|
|
10517
|
+
const fullPath = path47.join(dir, entry.name);
|
|
10101
10518
|
try {
|
|
10102
10519
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
10103
10520
|
const fm = extractFrontmatterBlock(raw);
|
|
@@ -10134,7 +10551,7 @@ async function runIntakeBranch(opts) {
|
|
|
10134
10551
|
labelFilter = "cleargate:proposal",
|
|
10135
10552
|
now = () => (/* @__PURE__ */ new Date()).toISOString()
|
|
10136
10553
|
} = opts;
|
|
10137
|
-
const pendingSyncDir =
|
|
10554
|
+
const pendingSyncDir = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
10138
10555
|
let remoteItems = [];
|
|
10139
10556
|
try {
|
|
10140
10557
|
remoteItems = await mcp2.call(
|
|
@@ -10165,7 +10582,7 @@ async function runIntakeBranch(opts) {
|
|
|
10165
10582
|
const slug2 = slugify(item.title ?? "untitled");
|
|
10166
10583
|
const num2 = proposalId2.replace("PROP-", "");
|
|
10167
10584
|
const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
|
|
10168
|
-
const targetPath2 =
|
|
10585
|
+
const targetPath2 = path48.join(pendingSyncDir, filename2);
|
|
10169
10586
|
createdItems.push({
|
|
10170
10587
|
proposalId: proposalId2,
|
|
10171
10588
|
remoteId: item.remote_id,
|
|
@@ -10178,7 +10595,7 @@ async function runIntakeBranch(opts) {
|
|
|
10178
10595
|
const num = proposalId.replace("PROP-", "");
|
|
10179
10596
|
const slug = slugify(item.title ?? "untitled");
|
|
10180
10597
|
const filename = `PROPOSAL-${num}-remote-${slug}.md`;
|
|
10181
|
-
const targetPath =
|
|
10598
|
+
const targetPath = path48.join(pendingSyncDir, filename);
|
|
10182
10599
|
const nowTs = now();
|
|
10183
10600
|
const fm = {
|
|
10184
10601
|
proposal_id: proposalId,
|
|
@@ -10270,8 +10687,8 @@ path/to/new/file.ext - {Explanation of purpose}
|
|
|
10270
10687
|
}
|
|
10271
10688
|
async function hasAnyRemoteAuthored(projectRoot) {
|
|
10272
10689
|
const dirs = [
|
|
10273
|
-
|
|
10274
|
-
|
|
10690
|
+
path48.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
10691
|
+
path48.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
10275
10692
|
];
|
|
10276
10693
|
for (const dir of dirs) {
|
|
10277
10694
|
let entries;
|
|
@@ -10282,7 +10699,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
10282
10699
|
}
|
|
10283
10700
|
for (const entry of entries) {
|
|
10284
10701
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10285
|
-
const fullPath =
|
|
10702
|
+
const fullPath = path48.join(dir, entry.name);
|
|
10286
10703
|
try {
|
|
10287
10704
|
const raw = await fsPromises4.readFile(fullPath, "utf8");
|
|
10288
10705
|
const fmEnd = raw.indexOf("\n---", 4);
|
|
@@ -10300,9 +10717,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
10300
10717
|
|
|
10301
10718
|
// src/lib/active-criteria.ts
|
|
10302
10719
|
init_cjs_shims();
|
|
10303
|
-
var
|
|
10720
|
+
var fs44 = __toESM(require("fs"), 1);
|
|
10304
10721
|
var fsPromises5 = __toESM(require("fs/promises"), 1);
|
|
10305
|
-
var
|
|
10722
|
+
var path49 = __toESM(require("path"), 1);
|
|
10306
10723
|
async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
10307
10724
|
const active = /* @__PURE__ */ new Set();
|
|
10308
10725
|
const now = Date.parse(nowFn());
|
|
@@ -10327,7 +10744,7 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
10327
10744
|
const ids = /* @__PURE__ */ new Set();
|
|
10328
10745
|
try {
|
|
10329
10746
|
const sprintDir = resolveActiveSprintDir(projectRoot);
|
|
10330
|
-
const sprintId =
|
|
10747
|
+
const sprintId = path49.basename(sprintDir);
|
|
10331
10748
|
if (sprintId === "_off-sprint") return ids;
|
|
10332
10749
|
const sprintFile = await findSprintFile2(projectRoot, sprintId);
|
|
10333
10750
|
if (!sprintFile) return ids;
|
|
@@ -10342,14 +10759,14 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
10342
10759
|
return ids;
|
|
10343
10760
|
}
|
|
10344
10761
|
async function findSprintFile2(projectRoot, sprintId) {
|
|
10345
|
-
const pendingSync =
|
|
10346
|
-
const archive =
|
|
10762
|
+
const pendingSync = path49.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
10763
|
+
const archive = path49.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
10347
10764
|
for (const dir of [pendingSync, archive]) {
|
|
10348
10765
|
try {
|
|
10349
|
-
const entries =
|
|
10766
|
+
const entries = fs44.readdirSync(dir, { withFileTypes: true });
|
|
10350
10767
|
for (const entry of entries) {
|
|
10351
10768
|
if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
|
|
10352
|
-
return
|
|
10769
|
+
return path49.join(dir, entry.name);
|
|
10353
10770
|
}
|
|
10354
10771
|
}
|
|
10355
10772
|
} catch {
|
|
@@ -10361,12 +10778,12 @@ async function findSprintFile2(projectRoot, sprintId) {
|
|
|
10361
10778
|
// src/lib/comments-cache.ts
|
|
10362
10779
|
init_cjs_shims();
|
|
10363
10780
|
var fsPromises6 = __toESM(require("fs/promises"), 1);
|
|
10364
|
-
var
|
|
10781
|
+
var path50 = __toESM(require("path"), 1);
|
|
10365
10782
|
function cacheDir(projectRoot) {
|
|
10366
|
-
return
|
|
10783
|
+
return path50.join(projectRoot, ".cleargate", ".comments-cache");
|
|
10367
10784
|
}
|
|
10368
10785
|
function cachePath(projectRoot, remoteId) {
|
|
10369
|
-
return
|
|
10786
|
+
return path50.join(cacheDir(projectRoot), `${remoteId}.json`);
|
|
10370
10787
|
}
|
|
10371
10788
|
async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
10372
10789
|
const dir = cacheDir(projectRoot);
|
|
@@ -10381,7 +10798,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
|
10381
10798
|
// src/lib/wiki-comments-render.ts
|
|
10382
10799
|
init_cjs_shims();
|
|
10383
10800
|
var fsPromises7 = __toESM(require("fs/promises"), 1);
|
|
10384
|
-
var
|
|
10801
|
+
var path51 = __toESM(require("path"), 1);
|
|
10385
10802
|
var START = "<!-- cleargate:comments:start -->";
|
|
10386
10803
|
var END = "<!-- cleargate:comments:end -->";
|
|
10387
10804
|
function resolveBucket(fm) {
|
|
@@ -10426,7 +10843,7 @@ async function renderCommentsSection(opts) {
|
|
|
10426
10843
|
const bucket = resolveBucket(localItem.fm);
|
|
10427
10844
|
const primaryId = getPrimaryId(localItem.fm);
|
|
10428
10845
|
if (!bucket || !primaryId) return;
|
|
10429
|
-
const wikiPath =
|
|
10846
|
+
const wikiPath = path51.join(
|
|
10430
10847
|
projectRoot,
|
|
10431
10848
|
".cleargate",
|
|
10432
10849
|
"wiki",
|
|
@@ -10462,7 +10879,7 @@ async function renderCommentsSection(opts) {
|
|
|
10462
10879
|
await writeAtomic4(wikiPath, updated);
|
|
10463
10880
|
}
|
|
10464
10881
|
async function writeAtomic4(filePath, content) {
|
|
10465
|
-
await fsPromises7.mkdir(
|
|
10882
|
+
await fsPromises7.mkdir(path51.dirname(filePath), { recursive: true });
|
|
10466
10883
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
10467
10884
|
await fsPromises7.writeFile(tmpPath, content, "utf8");
|
|
10468
10885
|
await fsPromises7.rename(tmpPath, filePath);
|
|
@@ -10474,11 +10891,11 @@ async function syncCheckHandler(opts = {}) {
|
|
|
10474
10891
|
const env = opts.env ?? process.env;
|
|
10475
10892
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
10476
10893
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
10477
|
-
const markerPath =
|
|
10894
|
+
const markerPath = path52.join(projectRoot, ".cleargate", ".sync-marker.json");
|
|
10478
10895
|
const updateMarker = async (nowIso2) => {
|
|
10479
10896
|
try {
|
|
10480
10897
|
const content = JSON.stringify({ last_check: nowIso2 });
|
|
10481
|
-
await fsPromises8.mkdir(
|
|
10898
|
+
await fsPromises8.mkdir(path52.dirname(markerPath), { recursive: true });
|
|
10482
10899
|
const tmpPath = `${markerPath}.tmp.${Date.now()}`;
|
|
10483
10900
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
10484
10901
|
await fsPromises8.rename(tmpPath, markerPath);
|
|
@@ -10561,7 +10978,7 @@ async function syncHandler(opts = {}) {
|
|
|
10561
10978
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
10562
10979
|
const identity = resolveIdentity(projectRoot);
|
|
10563
10980
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
10564
|
-
const sprintId =
|
|
10981
|
+
const sprintId = path52.basename(sprintRoot);
|
|
10565
10982
|
let mcp2;
|
|
10566
10983
|
if (opts.mcp) {
|
|
10567
10984
|
mcp2 = opts.mcp;
|
|
@@ -10622,7 +11039,7 @@ async function syncHandler(opts = {}) {
|
|
|
10622
11039
|
exit(2);
|
|
10623
11040
|
return;
|
|
10624
11041
|
}
|
|
10625
|
-
const wikiMetaPath =
|
|
11042
|
+
const wikiMetaPath = path52.join(projectRoot, ".cleargate", "wiki", "meta.json");
|
|
10626
11043
|
let lastRemoteSync = "1970-01-01T00:00:00.000Z";
|
|
10627
11044
|
try {
|
|
10628
11045
|
const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -10863,7 +11280,7 @@ async function syncHandler(opts = {}) {
|
|
|
10863
11280
|
};
|
|
10864
11281
|
await appendSyncLog(sprintRoot, entry);
|
|
10865
11282
|
}
|
|
10866
|
-
const conflictsFile =
|
|
11283
|
+
const conflictsFile = path52.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
10867
11284
|
const conflictsContent = {
|
|
10868
11285
|
generated_at: nowFn(),
|
|
10869
11286
|
sprint_id: sprintId,
|
|
@@ -10871,7 +11288,7 @@ async function syncHandler(opts = {}) {
|
|
|
10871
11288
|
};
|
|
10872
11289
|
await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
|
|
10873
11290
|
try {
|
|
10874
|
-
await fsPromises8.mkdir(
|
|
11291
|
+
await fsPromises8.mkdir(path52.dirname(wikiMetaPath), { recursive: true });
|
|
10875
11292
|
let meta = {};
|
|
10876
11293
|
try {
|
|
10877
11294
|
const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -10912,13 +11329,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
|
|
|
10912
11329
|
await writeAtomic5(localPath, newContent);
|
|
10913
11330
|
}
|
|
10914
11331
|
async function writeAtomic5(filePath, content) {
|
|
10915
|
-
await fsPromises8.mkdir(
|
|
11332
|
+
await fsPromises8.mkdir(path52.dirname(filePath), { recursive: true });
|
|
10916
11333
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
10917
11334
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
10918
11335
|
await fsPromises8.rename(tmpPath, filePath);
|
|
10919
11336
|
}
|
|
10920
11337
|
async function scanLocalItems(projectRoot) {
|
|
10921
|
-
const pendingSync =
|
|
11338
|
+
const pendingSync = path52.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
10922
11339
|
const results = [];
|
|
10923
11340
|
let entries;
|
|
10924
11341
|
try {
|
|
@@ -10928,7 +11345,7 @@ async function scanLocalItems(projectRoot) {
|
|
|
10928
11345
|
}
|
|
10929
11346
|
for (const entry of entries) {
|
|
10930
11347
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10931
|
-
const fullPath =
|
|
11348
|
+
const fullPath = path52.join(pendingSync, entry.name);
|
|
10932
11349
|
try {
|
|
10933
11350
|
const raw = await fsPromises8.readFile(fullPath, "utf8");
|
|
10934
11351
|
const { fm, body } = parseFrontmatter(raw);
|
|
@@ -10956,7 +11373,7 @@ init_config();
|
|
|
10956
11373
|
// src/lib/sync/work-items.ts
|
|
10957
11374
|
init_cjs_shims();
|
|
10958
11375
|
var fsPromises9 = __toESM(require("fs/promises"), 1);
|
|
10959
|
-
var
|
|
11376
|
+
var path53 = __toESM(require("path"), 1);
|
|
10960
11377
|
var import_node_crypto2 = require("crypto");
|
|
10961
11378
|
var BATCH_SIZE = 100;
|
|
10962
11379
|
var ATTRIBUTION_FIELDS = /* @__PURE__ */ new Set([
|
|
@@ -11004,8 +11421,8 @@ function getItemId2(fm) {
|
|
|
11004
11421
|
}
|
|
11005
11422
|
async function walkDeliveryDirs(projectRoot) {
|
|
11006
11423
|
const dirs = [
|
|
11007
|
-
|
|
11008
|
-
|
|
11424
|
+
path53.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
11425
|
+
path53.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
11009
11426
|
];
|
|
11010
11427
|
const results = [];
|
|
11011
11428
|
for (const dir of dirs) {
|
|
@@ -11017,7 +11434,7 @@ async function walkDeliveryDirs(projectRoot) {
|
|
|
11017
11434
|
}
|
|
11018
11435
|
for (const entry of entries) {
|
|
11019
11436
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11020
|
-
const fullPath =
|
|
11437
|
+
const fullPath = path53.join(dir, entry.name);
|
|
11021
11438
|
try {
|
|
11022
11439
|
const raw = await fsPromises9.readFile(fullPath, "utf8");
|
|
11023
11440
|
const { fm, body } = parseFrontmatter(raw);
|
|
@@ -11042,7 +11459,7 @@ async function walkDeliveryDirs(projectRoot) {
|
|
|
11042
11459
|
return results;
|
|
11043
11460
|
}
|
|
11044
11461
|
async function writeAtomic6(filePath, content) {
|
|
11045
|
-
await fsPromises9.mkdir(
|
|
11462
|
+
await fsPromises9.mkdir(path53.dirname(filePath), { recursive: true });
|
|
11046
11463
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11047
11464
|
await fsPromises9.writeFile(tmpPath, content, "utf8");
|
|
11048
11465
|
await fsPromises9.rename(tmpPath, filePath);
|
|
@@ -11109,9 +11526,9 @@ async function syncWorkItems(opts) {
|
|
|
11109
11526
|
|
|
11110
11527
|
// src/lib/admin-url.ts
|
|
11111
11528
|
init_cjs_shims();
|
|
11112
|
-
var
|
|
11529
|
+
var fs45 = __toESM(require("fs"), 1);
|
|
11113
11530
|
var os10 = __toESM(require("os"), 1);
|
|
11114
|
-
var
|
|
11531
|
+
var path54 = __toESM(require("path"), 1);
|
|
11115
11532
|
var DEFAULT_BASE = "https://admin.cleargate.soula.ge/";
|
|
11116
11533
|
function adminUrl(urlPath, opts) {
|
|
11117
11534
|
const env = opts?.env ?? process.env;
|
|
@@ -11134,8 +11551,8 @@ function adminUrl(urlPath, opts) {
|
|
|
11134
11551
|
function readLocalConfig() {
|
|
11135
11552
|
const home = os10.homedir();
|
|
11136
11553
|
if (!home) return null;
|
|
11137
|
-
const configPath =
|
|
11138
|
-
const raw =
|
|
11554
|
+
const configPath = path54.join(home, ".cleargate", "config.json");
|
|
11555
|
+
const raw = fs45.readFileSync(configPath, "utf8");
|
|
11139
11556
|
return JSON.parse(raw);
|
|
11140
11557
|
}
|
|
11141
11558
|
|
|
@@ -11250,7 +11667,7 @@ async function syncWorkItemsHandler(opts = {}) {
|
|
|
11250
11667
|
// src/commands/pull.ts
|
|
11251
11668
|
init_cjs_shims();
|
|
11252
11669
|
var fsPromises10 = __toESM(require("fs/promises"), 1);
|
|
11253
|
-
var
|
|
11670
|
+
var path55 = __toESM(require("path"), 1);
|
|
11254
11671
|
init_acquire();
|
|
11255
11672
|
init_config();
|
|
11256
11673
|
async function pullHandler(idOrRemoteId, opts = {}) {
|
|
@@ -11365,7 +11782,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
11365
11782
|
result: "ok"
|
|
11366
11783
|
};
|
|
11367
11784
|
await appendSyncLog(sprintRoot, entry);
|
|
11368
|
-
stdout(`pull: ${remoteId} applied to ${
|
|
11785
|
+
stdout(`pull: ${remoteId} applied to ${path55.relative(projectRoot, localPath)}
|
|
11369
11786
|
`);
|
|
11370
11787
|
if (opts.comments) {
|
|
11371
11788
|
const comments = await mcp2.call(
|
|
@@ -11388,7 +11805,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
11388
11805
|
if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
|
|
11389
11806
|
return idOrRemoteId;
|
|
11390
11807
|
}
|
|
11391
|
-
const pendingSync =
|
|
11808
|
+
const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
11392
11809
|
let entries;
|
|
11393
11810
|
try {
|
|
11394
11811
|
entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -11398,7 +11815,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
11398
11815
|
for (const entry of entries) {
|
|
11399
11816
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11400
11817
|
try {
|
|
11401
|
-
const raw = await fsPromises10.readFile(
|
|
11818
|
+
const raw = await fsPromises10.readFile(path55.join(pendingSync, entry.name), "utf8");
|
|
11402
11819
|
const { fm } = parseFrontmatter(raw);
|
|
11403
11820
|
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
11404
11821
|
if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
|
|
@@ -11411,7 +11828,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
11411
11828
|
return null;
|
|
11412
11829
|
}
|
|
11413
11830
|
async function findLocalFile(remoteId, projectRoot) {
|
|
11414
|
-
const pendingSync =
|
|
11831
|
+
const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
11415
11832
|
let entries;
|
|
11416
11833
|
try {
|
|
11417
11834
|
entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -11420,7 +11837,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
11420
11837
|
}
|
|
11421
11838
|
for (const entry of entries) {
|
|
11422
11839
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11423
|
-
const fullPath =
|
|
11840
|
+
const fullPath = path55.join(pendingSync, entry.name);
|
|
11424
11841
|
try {
|
|
11425
11842
|
const raw = await fsPromises10.readFile(fullPath, "utf8");
|
|
11426
11843
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -11431,7 +11848,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
11431
11848
|
return null;
|
|
11432
11849
|
}
|
|
11433
11850
|
async function writeAtomic7(filePath, content) {
|
|
11434
|
-
await fsPromises10.mkdir(
|
|
11851
|
+
await fsPromises10.mkdir(path55.dirname(filePath), { recursive: true });
|
|
11435
11852
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11436
11853
|
await fsPromises10.writeFile(tmpPath, content, "utf8");
|
|
11437
11854
|
await fsPromises10.rename(tmpPath, filePath);
|
|
@@ -11446,8 +11863,9 @@ function getItemId3(fm) {
|
|
|
11446
11863
|
|
|
11447
11864
|
// src/commands/push.ts
|
|
11448
11865
|
init_cjs_shims();
|
|
11866
|
+
var fs46 = __toESM(require("fs"), 1);
|
|
11449
11867
|
var fsPromises11 = __toESM(require("fs/promises"), 1);
|
|
11450
|
-
var
|
|
11868
|
+
var path56 = __toESM(require("path"), 1);
|
|
11451
11869
|
init_acquire();
|
|
11452
11870
|
init_config();
|
|
11453
11871
|
async function pushHandler(fileOrId, opts = {}) {
|
|
@@ -11457,6 +11875,13 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
11457
11875
|
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
11458
11876
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
11459
11877
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11878
|
+
const migrationLockPath = path56.join(projectRoot, ".cleargate", ".migration-lock");
|
|
11879
|
+
if (fs46.existsSync(migrationLockPath)) {
|
|
11880
|
+
stderr(`Error: CR-067 migration in progress (.migration-lock held); retry in 30s
|
|
11881
|
+
`);
|
|
11882
|
+
exit(75);
|
|
11883
|
+
return;
|
|
11884
|
+
}
|
|
11460
11885
|
const identity = resolveIdentity(projectRoot);
|
|
11461
11886
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
11462
11887
|
async function resolveMcp() {
|
|
@@ -11523,7 +11948,20 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
11523
11948
|
}
|
|
11524
11949
|
async function handlePush(filePath, ctx) {
|
|
11525
11950
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
11526
|
-
const resolvedPath =
|
|
11951
|
+
const resolvedPath = path56.isAbsolute(filePath) ? filePath : path56.resolve(projectRoot, filePath);
|
|
11952
|
+
if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
|
|
11953
|
+
if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
|
|
11954
|
+
stderr(
|
|
11955
|
+
`Error: path not in allowlist. Only sprint report files are accepted from sprint-runs/.
|
|
11956
|
+
Allowed: .cleargate/sprint-runs/SPRINT-NN/REPORT.md
|
|
11957
|
+
.cleargate/sprint-runs/SPRINT-NN/SPRINT-NN_REPORT.md
|
|
11958
|
+
Got: "${resolvedPath}"
|
|
11959
|
+
`
|
|
11960
|
+
);
|
|
11961
|
+
exit(2);
|
|
11962
|
+
return;
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11527
11965
|
let rawContent;
|
|
11528
11966
|
try {
|
|
11529
11967
|
rawContent = await fsPromises11.readFile(resolvedPath, "utf8");
|
|
@@ -11553,7 +11991,7 @@ async function handlePush(filePath, ctx) {
|
|
|
11553
11991
|
return;
|
|
11554
11992
|
}
|
|
11555
11993
|
const itemId = getItemId4(fm);
|
|
11556
|
-
const type =
|
|
11994
|
+
const type = getItemTypeWithPathOverride(resolvedPath, fm);
|
|
11557
11995
|
if (!type) {
|
|
11558
11996
|
stderr(`Error: cannot determine item type from frontmatter in "${resolvedPath}".
|
|
11559
11997
|
`);
|
|
@@ -11566,6 +12004,9 @@ async function handlePush(filePath, ctx) {
|
|
|
11566
12004
|
if (h1) payloadForPush["title"] = h1;
|
|
11567
12005
|
}
|
|
11568
12006
|
payloadForPush["body"] = body;
|
|
12007
|
+
if (payloadForPush["origin"] === void 0) {
|
|
12008
|
+
payloadForPush["origin"] = "cleargate-cli";
|
|
12009
|
+
}
|
|
11569
12010
|
const mcp2 = await resolveMcp();
|
|
11570
12011
|
let result;
|
|
11571
12012
|
try {
|
|
@@ -11649,8 +12090,8 @@ async function handleRevert(idOrRemoteId, ctx) {
|
|
|
11649
12090
|
void localPath;
|
|
11650
12091
|
}
|
|
11651
12092
|
async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
11652
|
-
const pendingSync =
|
|
11653
|
-
const archive =
|
|
12093
|
+
const pendingSync = path56.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
12094
|
+
const archive = path56.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
11654
12095
|
for (const dir of [pendingSync, archive]) {
|
|
11655
12096
|
let entries;
|
|
11656
12097
|
try {
|
|
@@ -11660,7 +12101,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
11660
12101
|
}
|
|
11661
12102
|
for (const entry of entries) {
|
|
11662
12103
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11663
|
-
const fullPath =
|
|
12104
|
+
const fullPath = path56.join(dir, entry.name);
|
|
11664
12105
|
try {
|
|
11665
12106
|
const raw = await fsPromises11.readFile(fullPath, "utf8");
|
|
11666
12107
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -11679,23 +12120,26 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
11679
12120
|
return null;
|
|
11680
12121
|
}
|
|
11681
12122
|
async function writeAtomic8(filePath, content) {
|
|
11682
|
-
await fsPromises11.mkdir(
|
|
12123
|
+
await fsPromises11.mkdir(path56.dirname(filePath), { recursive: true });
|
|
11683
12124
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11684
12125
|
await fsPromises11.writeFile(tmpPath, content, "utf8");
|
|
11685
12126
|
await fsPromises11.rename(tmpPath, filePath);
|
|
11686
12127
|
}
|
|
11687
12128
|
function getItemId4(fm) {
|
|
11688
|
-
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
12129
|
+
for (const key of ["story_id", "epic_id", "proposal_id", "sprint_id", "cr_id", "bug_id"]) {
|
|
11689
12130
|
const val = fm[key];
|
|
11690
12131
|
if (typeof val === "string" && val) return val;
|
|
11691
12132
|
}
|
|
11692
12133
|
return "unknown";
|
|
11693
12134
|
}
|
|
12135
|
+
var SPRINT_RUNS_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]/;
|
|
12136
|
+
var SPRINT_REPORT_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]SPRINT-\d{2,}[\\/](REPORT|SPRINT-\d{2,}_REPORT)\.md$/;
|
|
11694
12137
|
function getItemType2(fm) {
|
|
11695
12138
|
const typeMap = {
|
|
11696
12139
|
story_id: "story",
|
|
11697
12140
|
epic_id: "epic",
|
|
11698
12141
|
proposal_id: "proposal",
|
|
12142
|
+
sprint_id: "sprint",
|
|
11699
12143
|
cr_id: "cr",
|
|
11700
12144
|
bug_id: "bug"
|
|
11701
12145
|
};
|
|
@@ -11704,11 +12148,15 @@ function getItemType2(fm) {
|
|
|
11704
12148
|
}
|
|
11705
12149
|
return null;
|
|
11706
12150
|
}
|
|
12151
|
+
function getItemTypeWithPathOverride(localPath, fm) {
|
|
12152
|
+
if (SPRINT_REPORT_PATH_REGEX.test(localPath)) return "sprint_report";
|
|
12153
|
+
return getItemType2(fm);
|
|
12154
|
+
}
|
|
11707
12155
|
|
|
11708
12156
|
// src/commands/conflicts.ts
|
|
11709
12157
|
init_cjs_shims();
|
|
11710
12158
|
var fsPromises12 = __toESM(require("fs/promises"), 1);
|
|
11711
|
-
var
|
|
12159
|
+
var path57 = __toESM(require("path"), 1);
|
|
11712
12160
|
init_acquire();
|
|
11713
12161
|
init_config();
|
|
11714
12162
|
var RESOLUTION_HINTS = {
|
|
@@ -11746,7 +12194,7 @@ async function conflictsHandler(opts = {}) {
|
|
|
11746
12194
|
}
|
|
11747
12195
|
}
|
|
11748
12196
|
}
|
|
11749
|
-
const conflictsFile =
|
|
12197
|
+
const conflictsFile = path57.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
11750
12198
|
let data;
|
|
11751
12199
|
try {
|
|
11752
12200
|
const raw = await fsPromises12.readFile(conflictsFile, "utf8");
|
|
@@ -11834,8 +12282,8 @@ function formatEntry(entry) {
|
|
|
11834
12282
|
|
|
11835
12283
|
// src/commands/admin-login.ts
|
|
11836
12284
|
init_cjs_shims();
|
|
11837
|
-
var
|
|
11838
|
-
var
|
|
12285
|
+
var fs47 = __toESM(require("fs"), 1);
|
|
12286
|
+
var path58 = __toESM(require("path"), 1);
|
|
11839
12287
|
var os11 = __toESM(require("os"), 1);
|
|
11840
12288
|
var DEFAULT_MCP_URL = "http://localhost:3000";
|
|
11841
12289
|
function resolveMcpUrl(mcpUrlFlag, env) {
|
|
@@ -11844,14 +12292,14 @@ function resolveMcpUrl(mcpUrlFlag, env) {
|
|
|
11844
12292
|
function resolveAuthFilePath(opts) {
|
|
11845
12293
|
if (opts.authFilePath) return opts.authFilePath;
|
|
11846
12294
|
const homedirFn = opts.homedir ?? os11.homedir;
|
|
11847
|
-
return
|
|
12295
|
+
return path58.join(homedirFn(), ".cleargate", "admin-auth.json");
|
|
11848
12296
|
}
|
|
11849
12297
|
function writeAdminAuth(filePath, token) {
|
|
11850
|
-
const dir =
|
|
11851
|
-
|
|
12298
|
+
const dir = path58.dirname(filePath);
|
|
12299
|
+
fs47.mkdirSync(dir, { recursive: true });
|
|
11852
12300
|
const payload = JSON.stringify({ version: 1, token }, null, 2);
|
|
11853
|
-
|
|
11854
|
-
|
|
12301
|
+
fs47.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
|
|
12302
|
+
fs47.chmodSync(filePath, 384);
|
|
11855
12303
|
}
|
|
11856
12304
|
async function adminLoginHandler(opts = {}) {
|
|
11857
12305
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
@@ -11961,8 +12409,8 @@ async function adminLoginHandler(opts = {}) {
|
|
|
11961
12409
|
|
|
11962
12410
|
// src/commands/hotfix.ts
|
|
11963
12411
|
init_cjs_shims();
|
|
11964
|
-
var
|
|
11965
|
-
var
|
|
12412
|
+
var fs48 = __toESM(require("fs"), 1);
|
|
12413
|
+
var path59 = __toESM(require("path"), 1);
|
|
11966
12414
|
function defaultExit4(code) {
|
|
11967
12415
|
return process.exit(code);
|
|
11968
12416
|
}
|
|
@@ -11972,7 +12420,7 @@ function maxHotfixId(pendingDir) {
|
|
|
11972
12420
|
let max = 0;
|
|
11973
12421
|
let entries;
|
|
11974
12422
|
try {
|
|
11975
|
-
entries =
|
|
12423
|
+
entries = fs48.readdirSync(pendingDir);
|
|
11976
12424
|
} catch {
|
|
11977
12425
|
return 0;
|
|
11978
12426
|
}
|
|
@@ -11986,13 +12434,13 @@ function maxHotfixId(pendingDir) {
|
|
|
11986
12434
|
return max;
|
|
11987
12435
|
}
|
|
11988
12436
|
function countActiveHotfixes(repoRoot) {
|
|
11989
|
-
const pendingDir =
|
|
11990
|
-
const archiveDir =
|
|
12437
|
+
const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
12438
|
+
const archiveDir = path59.join(repoRoot, ".cleargate", "delivery", "archive");
|
|
11991
12439
|
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
11992
12440
|
let count = 0;
|
|
11993
12441
|
let pendingEntries = [];
|
|
11994
12442
|
try {
|
|
11995
|
-
pendingEntries =
|
|
12443
|
+
pendingEntries = fs48.readdirSync(pendingDir);
|
|
11996
12444
|
} catch {
|
|
11997
12445
|
}
|
|
11998
12446
|
for (const entry of pendingEntries) {
|
|
@@ -12000,13 +12448,13 @@ function countActiveHotfixes(repoRoot) {
|
|
|
12000
12448
|
}
|
|
12001
12449
|
let archiveEntries = [];
|
|
12002
12450
|
try {
|
|
12003
|
-
archiveEntries =
|
|
12451
|
+
archiveEntries = fs48.readdirSync(archiveDir);
|
|
12004
12452
|
} catch {
|
|
12005
12453
|
}
|
|
12006
12454
|
for (const entry of archiveEntries) {
|
|
12007
12455
|
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
|
|
12008
12456
|
try {
|
|
12009
|
-
const stat =
|
|
12457
|
+
const stat = fs48.statSync(path59.join(archiveDir, entry));
|
|
12010
12458
|
if (stat.mtimeMs >= sevenDaysAgo) count++;
|
|
12011
12459
|
} catch {
|
|
12012
12460
|
}
|
|
@@ -12015,7 +12463,7 @@ function countActiveHotfixes(repoRoot) {
|
|
|
12015
12463
|
return count;
|
|
12016
12464
|
}
|
|
12017
12465
|
function resolveTemplatePath(repoRoot) {
|
|
12018
|
-
return
|
|
12466
|
+
return path59.join(repoRoot, ".cleargate", "templates", "hotfix.md");
|
|
12019
12467
|
}
|
|
12020
12468
|
function hotfixNewHandler(opts, cli) {
|
|
12021
12469
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -12034,14 +12482,14 @@ function hotfixNewHandler(opts, cli) {
|
|
|
12034
12482
|
);
|
|
12035
12483
|
return exitFn(1);
|
|
12036
12484
|
}
|
|
12037
|
-
const pendingDir =
|
|
12485
|
+
const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
12038
12486
|
const maxId = maxHotfixId(pendingDir);
|
|
12039
12487
|
const nextId = maxId + 1;
|
|
12040
12488
|
const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
|
|
12041
12489
|
const templatePath = resolveTemplatePath(repoRoot);
|
|
12042
12490
|
let templateContent;
|
|
12043
12491
|
try {
|
|
12044
|
-
templateContent =
|
|
12492
|
+
templateContent = fs48.readFileSync(templatePath, "utf8");
|
|
12045
12493
|
} catch {
|
|
12046
12494
|
stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
|
|
12047
12495
|
return exitFn(2);
|
|
@@ -12049,10 +12497,10 @@ function hotfixNewHandler(opts, cli) {
|
|
|
12049
12497
|
const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
|
|
12050
12498
|
const fileSlug = opts.slug.replace(/-/g, "_");
|
|
12051
12499
|
const fileName = `${idStr}_${fileSlug}.md`;
|
|
12052
|
-
const outPath =
|
|
12500
|
+
const outPath = path59.join(pendingDir, fileName);
|
|
12053
12501
|
try {
|
|
12054
|
-
|
|
12055
|
-
|
|
12502
|
+
fs48.mkdirSync(pendingDir, { recursive: true });
|
|
12503
|
+
fs48.writeFileSync(outPath, content, "utf8");
|
|
12056
12504
|
} catch (err) {
|
|
12057
12505
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12058
12506
|
stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
|
|
@@ -12140,8 +12588,26 @@ var AuthFetcher = class {
|
|
|
12140
12588
|
}
|
|
12141
12589
|
};
|
|
12142
12590
|
|
|
12591
|
+
// src/auth/service-token-fetcher.ts
|
|
12592
|
+
init_cjs_shims();
|
|
12593
|
+
var ServiceTokenFetcher = class {
|
|
12594
|
+
constructor(token) {
|
|
12595
|
+
this.token = token;
|
|
12596
|
+
}
|
|
12597
|
+
token;
|
|
12598
|
+
async getAccessToken() {
|
|
12599
|
+
return this.token;
|
|
12600
|
+
}
|
|
12601
|
+
};
|
|
12602
|
+
|
|
12143
12603
|
// src/commands/mcp-serve.ts
|
|
12144
12604
|
var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
|
|
12605
|
+
var ServiceToken401Error = class extends Error {
|
|
12606
|
+
constructor() {
|
|
12607
|
+
super("service-token-401");
|
|
12608
|
+
this.name = "ServiceToken401Error";
|
|
12609
|
+
}
|
|
12610
|
+
};
|
|
12145
12611
|
async function mcpServeHandler(opts) {
|
|
12146
12612
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
12147
12613
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
@@ -12151,32 +12617,44 @@ async function mcpServeHandler(opts) {
|
|
|
12151
12617
|
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
|
|
12152
12618
|
});
|
|
12153
12619
|
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
|
-
|
|
12620
|
+
const serviceToken = process.env["CLEARGATE_SERVICE_TOKEN"] ?? "";
|
|
12621
|
+
let fetcher;
|
|
12622
|
+
let isServiceTokenMode;
|
|
12623
|
+
if (serviceToken.length > 0) {
|
|
12624
|
+
isServiceTokenMode = true;
|
|
12625
|
+
fetcher = new ServiceTokenFetcher(serviceToken);
|
|
12626
|
+
stderr("cleargate mcp serve: auth mode = service-token\n");
|
|
12627
|
+
} else {
|
|
12628
|
+
isServiceTokenMode = false;
|
|
12629
|
+
const store = await (opts.createStore ?? createTokenStore)({
|
|
12630
|
+
...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
|
|
12631
|
+
...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
|
|
12632
|
+
});
|
|
12633
|
+
const authFetcher = new AuthFetcher({
|
|
12634
|
+
baseUrl,
|
|
12635
|
+
loadRefresh: () => store.load(opts.profile),
|
|
12636
|
+
saveRefresh: (t) => store.save(opts.profile, t),
|
|
12637
|
+
...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
|
|
12638
|
+
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
12639
|
+
});
|
|
12640
|
+
fetcher = authFetcher;
|
|
12641
|
+
stderr("cleargate mcp serve: auth mode = keychain-refresh\n");
|
|
12642
|
+
try {
|
|
12643
|
+
await authFetcher.getAccessToken();
|
|
12644
|
+
} catch (err) {
|
|
12645
|
+
if (err instanceof RefreshError) {
|
|
12646
|
+
stderr(
|
|
12647
|
+
`cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
|
|
12171
12648
|
`
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12649
|
+
);
|
|
12650
|
+
} else {
|
|
12651
|
+
stderr(
|
|
12652
|
+
`cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
|
|
12176
12653
|
`
|
|
12177
|
-
|
|
12654
|
+
);
|
|
12655
|
+
}
|
|
12656
|
+
return exit(1);
|
|
12178
12657
|
}
|
|
12179
|
-
return exit(1);
|
|
12180
12658
|
}
|
|
12181
12659
|
const inputStream = opts.stdin ?? process.stdin;
|
|
12182
12660
|
const rl = readline5.createInterface({
|
|
@@ -12187,12 +12665,27 @@ async function mcpServeHandler(opts) {
|
|
|
12187
12665
|
for await (const line of rl) {
|
|
12188
12666
|
if (!line.trim()) continue;
|
|
12189
12667
|
try {
|
|
12190
|
-
await proxyOne(
|
|
12668
|
+
await proxyOne(
|
|
12669
|
+
line,
|
|
12670
|
+
baseUrl,
|
|
12671
|
+
fetcher,
|
|
12672
|
+
isServiceTokenMode,
|
|
12673
|
+
fetchFn,
|
|
12674
|
+
stdout,
|
|
12675
|
+
stderr
|
|
12676
|
+
);
|
|
12191
12677
|
} catch (err) {
|
|
12678
|
+
if (err instanceof ServiceToken401Error) {
|
|
12679
|
+
stderr(
|
|
12680
|
+
`cleargate mcp serve: CLEARGATE_SERVICE_TOKEN rejected by /mcp (401). Issue a new token in the admin console: Tokens \u2192 Issue \u2192 copy snippet.
|
|
12681
|
+
`
|
|
12682
|
+
);
|
|
12683
|
+
return exit(1);
|
|
12684
|
+
}
|
|
12192
12685
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12193
12686
|
stderr(`cleargate mcp serve: proxy error: ${errMsg}
|
|
12194
12687
|
`);
|
|
12195
|
-
const id =
|
|
12688
|
+
const id = extractId2(line);
|
|
12196
12689
|
if (id !== void 0) {
|
|
12197
12690
|
stdout(
|
|
12198
12691
|
JSON.stringify({
|
|
@@ -12205,7 +12698,7 @@ async function mcpServeHandler(opts) {
|
|
|
12205
12698
|
}
|
|
12206
12699
|
}
|
|
12207
12700
|
}
|
|
12208
|
-
async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
12701
|
+
async function proxyOne(line, baseUrl, fetcher, isServiceTokenMode, fetchFn, stdout, stderr) {
|
|
12209
12702
|
let parsed;
|
|
12210
12703
|
try {
|
|
12211
12704
|
parsed = JSON.parse(line);
|
|
@@ -12218,6 +12711,9 @@ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
|
12218
12711
|
let access = await fetcher.getAccessToken();
|
|
12219
12712
|
let res = await postFrame(baseUrl, line, access, fetchFn);
|
|
12220
12713
|
if (res.status === 401) {
|
|
12714
|
+
if (isServiceTokenMode) {
|
|
12715
|
+
throw new ServiceToken401Error();
|
|
12716
|
+
}
|
|
12221
12717
|
fetcher.invalidate();
|
|
12222
12718
|
access = await fetcher.getAccessToken();
|
|
12223
12719
|
res = await postFrame(baseUrl, line, access, fetchFn);
|
|
@@ -12265,7 +12761,7 @@ async function streamSse(res, stdout) {
|
|
|
12265
12761
|
}
|
|
12266
12762
|
}
|
|
12267
12763
|
}
|
|
12268
|
-
function
|
|
12764
|
+
function extractId2(line) {
|
|
12269
12765
|
try {
|
|
12270
12766
|
const obj = JSON.parse(line);
|
|
12271
12767
|
return "id" in obj ? obj.id : void 0;
|
|
@@ -12437,8 +12933,8 @@ sprint.command("close <sprint-id>").description("close a sprint \u2014 validates
|
|
|
12437
12933
|
}
|
|
12438
12934
|
sprintCloseHandler(handlerOpts);
|
|
12439
12935
|
});
|
|
12440
|
-
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) => {
|
|
12441
|
-
reconcileLifecycleCliHandler({ sprintId, since: opts.since, until: opts.until });
|
|
12936
|
+
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) => {
|
|
12937
|
+
reconcileLifecycleCliHandler({ sprintId, since: opts.since, until: opts.until, parents: opts.parents });
|
|
12442
12938
|
});
|
|
12443
12939
|
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) => {
|
|
12444
12940
|
const handlerOpts = { sprintId };
|