cleargate 0.12.0 → 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 +13 -13
- package/dist/{chunk-HZPJ5QX4.js → chunk-EG6YGT2O.js} +315 -33
- package/dist/chunk-EG6YGT2O.js.map +1 -0
- package/dist/cli.cjs +612 -289
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +73 -37
- 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/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/scripts/close_sprint.mjs +73 -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 +2 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +13 -13
- package/package.json +8 -9
- 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/scripts/close_sprint.mjs +73 -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 +2 -0
- package/templates/cleargate-planning/MANIFEST.json +13 -13
- 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
|
|
|
@@ -4526,9 +4525,9 @@ function detectWorkItemTypeFromFm(fm) {
|
|
|
4526
4525
|
}
|
|
4527
4526
|
function detectWorkItemType(idOrPath) {
|
|
4528
4527
|
const upper = idOrPath.toUpperCase();
|
|
4529
|
-
const
|
|
4528
|
+
const basename14 = upper.split("/").pop() ?? upper;
|
|
4530
4529
|
for (const { prefix, type } of PREFIX_MAP2) {
|
|
4531
|
-
if (
|
|
4530
|
+
if (basename14.includes(prefix)) {
|
|
4532
4531
|
return type;
|
|
4533
4532
|
}
|
|
4534
4533
|
}
|
|
@@ -7115,24 +7114,312 @@ function gateRunHandler(name, opts, cli) {
|
|
|
7115
7114
|
|
|
7116
7115
|
// src/commands/sprint.ts
|
|
7117
7116
|
init_cjs_shims();
|
|
7118
|
-
var
|
|
7119
|
-
var
|
|
7117
|
+
var fs35 = __toESM(require("fs"), 1);
|
|
7118
|
+
var path37 = __toESM(require("path"), 1);
|
|
7120
7119
|
var import_node_child_process11 = require("child_process");
|
|
7121
7120
|
var import_js_yaml7 = __toESM(require("js-yaml"), 1);
|
|
7122
7121
|
|
|
7123
7122
|
// src/lib/lifecycle-reconcile.ts
|
|
7124
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();
|
|
7125
7130
|
var fs33 = __toESM(require("fs"), 1);
|
|
7126
7131
|
var path35 = __toESM(require("path"), 1);
|
|
7127
|
-
|
|
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"];
|
|
7128
7415
|
var VERB_STATUS_MAP = {
|
|
7129
7416
|
feat: {
|
|
7130
7417
|
types: ["STORY", "EPIC", "CR"],
|
|
7131
|
-
expected: [
|
|
7418
|
+
expected: [...ARTIFACT_GATE_EXPECTED]
|
|
7132
7419
|
},
|
|
7133
7420
|
fix: {
|
|
7134
7421
|
types: ["BUG", "HOTFIX"],
|
|
7135
|
-
expected: [
|
|
7422
|
+
expected: [...ARTIFACT_GATE_EXPECTED]
|
|
7136
7423
|
}
|
|
7137
7424
|
};
|
|
7138
7425
|
var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
|
|
@@ -7183,10 +7470,10 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
7183
7470
|
{ rel: "archive", inArchive: true }
|
|
7184
7471
|
];
|
|
7185
7472
|
for (const { rel, inArchive } of dirs) {
|
|
7186
|
-
const dir =
|
|
7473
|
+
const dir = path36.join(deliveryRoot, rel);
|
|
7187
7474
|
let entries;
|
|
7188
7475
|
try {
|
|
7189
|
-
entries =
|
|
7476
|
+
entries = fs34.readdirSync(dir);
|
|
7190
7477
|
} catch {
|
|
7191
7478
|
continue;
|
|
7192
7479
|
}
|
|
@@ -7194,7 +7481,7 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
7194
7481
|
(e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
|
|
7195
7482
|
);
|
|
7196
7483
|
if (match) {
|
|
7197
|
-
const absPath =
|
|
7484
|
+
const absPath = path36.join(dir, match);
|
|
7198
7485
|
return { absPath, inArchive, relPath: `${rel}/${match}` };
|
|
7199
7486
|
}
|
|
7200
7487
|
}
|
|
@@ -7203,7 +7490,7 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
7203
7490
|
function readArtifactStatus(absPath) {
|
|
7204
7491
|
let raw;
|
|
7205
7492
|
try {
|
|
7206
|
-
raw =
|
|
7493
|
+
raw = fs34.readFileSync(absPath, "utf8");
|
|
7207
7494
|
} catch {
|
|
7208
7495
|
return { status: null, carryOver: false };
|
|
7209
7496
|
}
|
|
@@ -7258,7 +7545,7 @@ function reconcileLifecycle(opts) {
|
|
|
7258
7545
|
if (carryOver) continue;
|
|
7259
7546
|
let expectedStatuses;
|
|
7260
7547
|
if (verb === "feat" && type === "BUG") {
|
|
7261
|
-
expectedStatuses = [
|
|
7548
|
+
expectedStatuses = [...ARTIFACT_GATE_EXPECTED];
|
|
7262
7549
|
} else if (!verbConfig.types.includes(type)) {
|
|
7263
7550
|
continue;
|
|
7264
7551
|
} else {
|
|
@@ -7270,7 +7557,7 @@ function reconcileLifecycle(opts) {
|
|
|
7270
7557
|
cleanIds.add(id);
|
|
7271
7558
|
idToItem.delete(id);
|
|
7272
7559
|
} else if (!idToItem.has(id)) {
|
|
7273
|
-
const expectedStr = expectedStatuses[0] ?? "
|
|
7560
|
+
const expectedStr = expectedStatuses[0] ?? "Completed";
|
|
7274
7561
|
idToItem.set(id, {
|
|
7275
7562
|
id,
|
|
7276
7563
|
type,
|
|
@@ -7300,7 +7587,7 @@ function reconcileDecomposition(opts) {
|
|
|
7300
7587
|
const { sprintPlanPath, deliveryRoot } = opts;
|
|
7301
7588
|
let raw;
|
|
7302
7589
|
try {
|
|
7303
|
-
raw =
|
|
7590
|
+
raw = fs34.readFileSync(sprintPlanPath, "utf8");
|
|
7304
7591
|
} catch {
|
|
7305
7592
|
return { missing: [], clean: 0 };
|
|
7306
7593
|
}
|
|
@@ -7312,11 +7599,11 @@ function reconcileDecomposition(opts) {
|
|
|
7312
7599
|
}
|
|
7313
7600
|
const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
|
|
7314
7601
|
const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
|
|
7315
|
-
const pendingDir =
|
|
7316
|
-
const archiveDir =
|
|
7602
|
+
const pendingDir = path36.join(deliveryRoot, "pending-sync");
|
|
7603
|
+
const archiveDir = path36.join(deliveryRoot, "archive");
|
|
7317
7604
|
function listMdFiles(dir) {
|
|
7318
7605
|
try {
|
|
7319
|
-
return
|
|
7606
|
+
return fs34.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
7320
7607
|
} catch {
|
|
7321
7608
|
return [];
|
|
7322
7609
|
}
|
|
@@ -7388,9 +7675,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
|
|
|
7388
7675
|
for (const f of files) {
|
|
7389
7676
|
if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
|
|
7390
7677
|
if (!f.includes(storyPrefix)) continue;
|
|
7391
|
-
const absPath =
|
|
7678
|
+
const absPath = path36.join(dir, f);
|
|
7392
7679
|
try {
|
|
7393
|
-
const raw =
|
|
7680
|
+
const raw = fs34.readFileSync(absPath, "utf8");
|
|
7394
7681
|
const { fm } = parseFrontmatter(raw);
|
|
7395
7682
|
const parentRef = fm["parent_epic_ref"];
|
|
7396
7683
|
if (parentRef === epicId) {
|
|
@@ -7405,9 +7692,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
|
|
|
7405
7692
|
function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
|
|
7406
7693
|
for (const f of pendingFiles) {
|
|
7407
7694
|
if (!f.startsWith("EPIC-")) continue;
|
|
7408
|
-
const absPath =
|
|
7695
|
+
const absPath = path36.join(pendingDir, f);
|
|
7409
7696
|
try {
|
|
7410
|
-
const raw =
|
|
7697
|
+
const raw = fs34.readFileSync(absPath, "utf8");
|
|
7411
7698
|
const { fm } = parseFrontmatter(raw);
|
|
7412
7699
|
const contextSource = fm["context_source"];
|
|
7413
7700
|
if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
|
|
@@ -7433,7 +7720,7 @@ var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandone
|
|
|
7433
7720
|
function resolveRunScript(opts) {
|
|
7434
7721
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
7435
7722
|
const cwd = opts.cwd ?? process.cwd();
|
|
7436
|
-
return
|
|
7723
|
+
return path37.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
7437
7724
|
}
|
|
7438
7725
|
function defaultExit(code) {
|
|
7439
7726
|
return process.exit(code);
|
|
@@ -7452,18 +7739,18 @@ function sprintInitHandler(opts, cli) {
|
|
|
7452
7739
|
if (mode === "v1") {
|
|
7453
7740
|
return printInertAndExit(stdoutFn, exitFn);
|
|
7454
7741
|
}
|
|
7455
|
-
const deliveryRoot =
|
|
7742
|
+
const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
|
|
7456
7743
|
let lifecycleInitMode = "warn";
|
|
7457
7744
|
let sprintPlanPath = null;
|
|
7458
|
-
const pendingDir =
|
|
7745
|
+
const pendingDir = path37.join(deliveryRoot, "pending-sync");
|
|
7459
7746
|
try {
|
|
7460
|
-
const entries =
|
|
7747
|
+
const entries = fs35.readdirSync(pendingDir);
|
|
7461
7748
|
const sprintFile = entries.find(
|
|
7462
7749
|
(e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
|
|
7463
7750
|
);
|
|
7464
7751
|
if (sprintFile) {
|
|
7465
|
-
sprintPlanPath =
|
|
7466
|
-
const raw =
|
|
7752
|
+
sprintPlanPath = path37.join(pendingDir, sprintFile);
|
|
7753
|
+
const raw = fs35.readFileSync(sprintPlanPath, "utf8");
|
|
7467
7754
|
const { fm } = parseFileFrontmatter(raw);
|
|
7468
7755
|
if (fm["lifecycle_init_mode"] === "block") {
|
|
7469
7756
|
lifecycleInitMode = "block";
|
|
@@ -7497,7 +7784,7 @@ function sprintInitHandler(opts, cli) {
|
|
|
7497
7784
|
stderrFn("[cleargate sprint init] lifecycle drift waived via --allow-drift flag");
|
|
7498
7785
|
if (sprintPlanPath) {
|
|
7499
7786
|
try {
|
|
7500
|
-
const rawSprint =
|
|
7787
|
+
const rawSprint = fs35.readFileSync(sprintPlanPath, "utf8");
|
|
7501
7788
|
const { fm, body } = parseFileFrontmatter(rawSprint);
|
|
7502
7789
|
const waiverLine = `lifecycle waiver: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} for ${lifecycleResult.drift.map((d) => d.id).join(", ")}`;
|
|
7503
7790
|
const currentContextSource = typeof fm["context_source"] === "string" ? fm["context_source"] : "";
|
|
@@ -7594,20 +7881,20 @@ function reconcileLifecycleCliHandler(opts, cli) {
|
|
|
7594
7881
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
7595
7882
|
const exitFn = cli?.exit ?? defaultExit;
|
|
7596
7883
|
const cwd = cli?.cwd ?? process.cwd();
|
|
7597
|
-
const deliveryRoot =
|
|
7884
|
+
const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
|
|
7598
7885
|
let since;
|
|
7599
7886
|
let until;
|
|
7600
7887
|
if (opts.since) {
|
|
7601
7888
|
since = new Date(opts.since);
|
|
7602
7889
|
} else {
|
|
7603
7890
|
try {
|
|
7604
|
-
const pendingDir =
|
|
7605
|
-
const entries =
|
|
7891
|
+
const pendingDir = path37.join(deliveryRoot, "pending-sync");
|
|
7892
|
+
const entries = fs35.readdirSync(pendingDir);
|
|
7606
7893
|
const sprintFile = entries.find(
|
|
7607
7894
|
(e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
|
|
7608
7895
|
);
|
|
7609
7896
|
if (sprintFile) {
|
|
7610
|
-
const raw =
|
|
7897
|
+
const raw = fs35.readFileSync(path37.join(pendingDir, sprintFile), "utf8");
|
|
7611
7898
|
const { fm } = parseFileFrontmatter(raw);
|
|
7612
7899
|
const startDate = fm["start_date"];
|
|
7613
7900
|
since = typeof startDate === "string" ? new Date(startDate) : new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
|
|
@@ -7629,22 +7916,50 @@ function reconcileLifecycleCliHandler(opts, cli) {
|
|
|
7629
7916
|
});
|
|
7630
7917
|
if (result.drift.length === 0) {
|
|
7631
7918
|
stdoutFn(`lifecycle: clean (${result.clean} artifacts reconciled)`);
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
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
|
+
}
|
|
7642
7932
|
}
|
|
7643
|
-
return exitFn(1);
|
|
7644
7933
|
} catch (err) {
|
|
7645
7934
|
stderrFn(`lifecycle reconciliation error: ${err instanceof Error ? err.message : String(err)}`);
|
|
7646
|
-
|
|
7935
|
+
if (!opts.parents) {
|
|
7936
|
+
return exitFn(1);
|
|
7937
|
+
}
|
|
7647
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;
|
|
7961
|
+
}
|
|
7962
|
+
return exitFn(0);
|
|
7648
7963
|
}
|
|
7649
7964
|
function parseFileFrontmatter(raw) {
|
|
7650
7965
|
const lines = raw.split("\n");
|
|
@@ -7688,8 +8003,8 @@ ${body}`;
|
|
|
7688
8003
|
}
|
|
7689
8004
|
function atomicWriteStr(filePath, content) {
|
|
7690
8005
|
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
7691
|
-
|
|
7692
|
-
|
|
8006
|
+
fs35.writeFileSync(tmp, content, "utf8");
|
|
8007
|
+
fs35.renameSync(tmp, filePath);
|
|
7693
8008
|
}
|
|
7694
8009
|
function deriveSprintBranchForArchive(sprintId) {
|
|
7695
8010
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -7703,7 +8018,7 @@ function stampFile(raw, status, completedAt) {
|
|
|
7703
8018
|
return serializeFileContent(fm, body);
|
|
7704
8019
|
}
|
|
7705
8020
|
function stampSprintClose(sprintPath, now) {
|
|
7706
|
-
const previousContent =
|
|
8021
|
+
const previousContent = fs35.readFileSync(sprintPath, "utf8");
|
|
7707
8022
|
const { fm, body } = parseFileFrontmatter(previousContent);
|
|
7708
8023
|
const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
|
|
7709
8024
|
const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
|
|
@@ -7763,14 +8078,14 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7763
8078
|
if (mode === "v1") {
|
|
7764
8079
|
return printInertAndExit(stdoutFn, exitFn);
|
|
7765
8080
|
}
|
|
7766
|
-
const stateFile =
|
|
7767
|
-
if (!
|
|
8081
|
+
const stateFile = path37.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
|
|
8082
|
+
if (!fs35.existsSync(stateFile)) {
|
|
7768
8083
|
stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
|
|
7769
8084
|
return exitFn(1);
|
|
7770
8085
|
}
|
|
7771
8086
|
let state2;
|
|
7772
8087
|
try {
|
|
7773
|
-
state2 = JSON.parse(
|
|
8088
|
+
state2 = JSON.parse(fs35.readFileSync(stateFile, "utf8"));
|
|
7774
8089
|
} catch (err) {
|
|
7775
8090
|
stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
|
|
7776
8091
|
return exitFn(1);
|
|
@@ -7782,18 +8097,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7782
8097
|
return exitFn(1);
|
|
7783
8098
|
}
|
|
7784
8099
|
const stateStories = state2.stories ?? {};
|
|
7785
|
-
const pendingDir =
|
|
7786
|
-
const archiveDir =
|
|
8100
|
+
const pendingDir = path37.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
8101
|
+
const archiveDir = path37.join(cwd, ".cleargate", "delivery", "archive");
|
|
7787
8102
|
let sprintFile = null;
|
|
7788
|
-
for (const entry of
|
|
8103
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7789
8104
|
if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
|
|
7790
|
-
sprintFile =
|
|
8105
|
+
sprintFile = path37.join(pendingDir, entry);
|
|
7791
8106
|
break;
|
|
7792
8107
|
}
|
|
7793
8108
|
}
|
|
7794
8109
|
let epicIds = [];
|
|
7795
|
-
if (sprintFile &&
|
|
7796
|
-
const { fm } = parseFileFrontmatter(
|
|
8110
|
+
if (sprintFile && fs35.existsSync(sprintFile)) {
|
|
8111
|
+
const { fm } = parseFileFrontmatter(fs35.readFileSync(sprintFile, "utf8"));
|
|
7797
8112
|
const epics = fm["epics"];
|
|
7798
8113
|
if (Array.isArray(epics)) {
|
|
7799
8114
|
epicIds = epics.map(String);
|
|
@@ -7803,15 +8118,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7803
8118
|
if (sprintFile) {
|
|
7804
8119
|
plan.push({
|
|
7805
8120
|
src: sprintFile,
|
|
7806
|
-
destName:
|
|
8121
|
+
destName: path37.basename(sprintFile),
|
|
7807
8122
|
status: "Completed"
|
|
7808
8123
|
});
|
|
7809
8124
|
}
|
|
7810
8125
|
for (const epicId of epicIds) {
|
|
7811
|
-
for (const entry of
|
|
8126
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7812
8127
|
if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
|
|
7813
8128
|
plan.push({
|
|
7814
|
-
src:
|
|
8129
|
+
src: path37.join(pendingDir, entry),
|
|
7815
8130
|
destName: entry,
|
|
7816
8131
|
status: "Approved"
|
|
7817
8132
|
});
|
|
@@ -7820,10 +8135,10 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7820
8135
|
}
|
|
7821
8136
|
const storyKeys = storyKeysForEpic(stateStories, epicId);
|
|
7822
8137
|
for (const storyId of storyKeys) {
|
|
7823
|
-
for (const entry of
|
|
8138
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7824
8139
|
if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
|
|
7825
8140
|
plan.push({
|
|
7826
|
-
src:
|
|
8141
|
+
src: path37.join(pendingDir, entry),
|
|
7827
8142
|
destName: entry,
|
|
7828
8143
|
status: "Done"
|
|
7829
8144
|
});
|
|
@@ -7835,13 +8150,13 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7835
8150
|
const storyIdsInState = new Set(Object.keys(stateStories));
|
|
7836
8151
|
const planSrcs = new Set(plan.map((p) => p.src));
|
|
7837
8152
|
const orphans = [];
|
|
7838
|
-
for (const entry of
|
|
8153
|
+
for (const entry of fs35.readdirSync(pendingDir)) {
|
|
7839
8154
|
if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
|
|
7840
|
-
const candidate =
|
|
8155
|
+
const candidate = path37.join(pendingDir, entry);
|
|
7841
8156
|
if (planSrcs.has(candidate)) continue;
|
|
7842
8157
|
let raw;
|
|
7843
8158
|
try {
|
|
7844
|
-
raw =
|
|
8159
|
+
raw = fs35.readFileSync(candidate, "utf8");
|
|
7845
8160
|
} catch {
|
|
7846
8161
|
continue;
|
|
7847
8162
|
}
|
|
@@ -7858,18 +8173,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7858
8173
|
}
|
|
7859
8174
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7860
8175
|
const sprintBranch = deriveSprintBranchForArchive(opts.sprintId);
|
|
7861
|
-
const activePath =
|
|
8176
|
+
const activePath = path37.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
7862
8177
|
if (opts.dryRun) {
|
|
7863
8178
|
stdoutFn(`[dry-run] Sprint archive plan for ${opts.sprintId}:`);
|
|
7864
8179
|
stdoutFn(` Sprint branch: ${sprintBranch}`);
|
|
7865
8180
|
stdoutFn(` Files to archive (${plan.length}):`);
|
|
7866
8181
|
for (const entry of plan) {
|
|
7867
8182
|
stdoutFn(
|
|
7868
|
-
` ${
|
|
8183
|
+
` ${path37.basename(entry.src)} \u2192 archive/${entry.destName} [stamp: status=${entry.status}, completed_at=<now>]`
|
|
7869
8184
|
);
|
|
7870
8185
|
}
|
|
7871
8186
|
if (orphans.length > 0) {
|
|
7872
|
-
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) =>
|
|
8187
|
+
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) => path37.basename(o)).join(", ")}`);
|
|
7873
8188
|
}
|
|
7874
8189
|
stdoutFn(` .active \u2192 "" (truncate)`);
|
|
7875
8190
|
stdoutFn(` git checkout main`);
|
|
@@ -7878,9 +8193,9 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7878
8193
|
return exitFn(0);
|
|
7879
8194
|
}
|
|
7880
8195
|
let sprintFileSnapshot = null;
|
|
7881
|
-
const wikiRoot =
|
|
7882
|
-
const wikiInitialised =
|
|
7883
|
-
if (sprintFile &&
|
|
8196
|
+
const wikiRoot = path37.join(cwd, ".cleargate", "wiki");
|
|
8197
|
+
const wikiInitialised = fs35.existsSync(wikiRoot);
|
|
8198
|
+
if (sprintFile && fs35.existsSync(sprintFile)) {
|
|
7884
8199
|
const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
|
|
7885
8200
|
sprintFileSnapshot = previousContent;
|
|
7886
8201
|
if (wikiInitialised) {
|
|
@@ -7907,15 +8222,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
7907
8222
|
}
|
|
7908
8223
|
}
|
|
7909
8224
|
for (const entry of plan) {
|
|
7910
|
-
if (!
|
|
8225
|
+
if (!fs35.existsSync(entry.src)) {
|
|
7911
8226
|
stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
|
|
7912
8227
|
continue;
|
|
7913
8228
|
}
|
|
7914
|
-
const raw =
|
|
8229
|
+
const raw = fs35.readFileSync(entry.src, "utf8");
|
|
7915
8230
|
const stamped = stampFile(raw, entry.status, completedAt);
|
|
7916
|
-
const dest =
|
|
8231
|
+
const dest = path37.join(archiveDir, entry.destName);
|
|
7917
8232
|
atomicWriteStr(entry.src, stamped);
|
|
7918
|
-
|
|
8233
|
+
fs35.renameSync(entry.src, dest);
|
|
7919
8234
|
stdoutFn(`archived: ${entry.destName}`);
|
|
7920
8235
|
}
|
|
7921
8236
|
try {
|
|
@@ -7974,14 +8289,14 @@ function checkPrevSprintCompleted(sprintId, cwd) {
|
|
|
7974
8289
|
const prevNum = sprintNum - 1;
|
|
7975
8290
|
const prevId = `SPRINT-${String(prevNum).padStart(2, "0")}`;
|
|
7976
8291
|
const prevIdAlt = `SPRINT-${prevNum}`;
|
|
7977
|
-
const sprintRunsBase =
|
|
8292
|
+
const sprintRunsBase = path37.join(cwd, ".cleargate", "sprint-runs");
|
|
7978
8293
|
let stateJson = null;
|
|
7979
8294
|
let resolvedPrevId = prevId;
|
|
7980
8295
|
for (const pid of [prevId, prevIdAlt]) {
|
|
7981
|
-
const stateFile =
|
|
7982
|
-
if (
|
|
8296
|
+
const stateFile = path37.join(sprintRunsBase, pid, "state.json");
|
|
8297
|
+
if (fs35.existsSync(stateFile)) {
|
|
7983
8298
|
try {
|
|
7984
|
-
const raw =
|
|
8299
|
+
const raw = fs35.readFileSync(stateFile, "utf8");
|
|
7985
8300
|
stateJson = JSON.parse(raw);
|
|
7986
8301
|
resolvedPrevId = pid;
|
|
7987
8302
|
break;
|
|
@@ -8084,21 +8399,21 @@ function checkMainClean(cwd, execFn) {
|
|
|
8084
8399
|
}
|
|
8085
8400
|
function findSprintFile(sprintId, cwd) {
|
|
8086
8401
|
const searchDirs = [
|
|
8087
|
-
|
|
8088
|
-
|
|
8402
|
+
path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
|
|
8403
|
+
path37.join(cwd, ".cleargate", "delivery", "archive")
|
|
8089
8404
|
];
|
|
8090
8405
|
for (const dir of searchDirs) {
|
|
8091
|
-
if (!
|
|
8406
|
+
if (!fs35.existsSync(dir)) continue;
|
|
8092
8407
|
let entries;
|
|
8093
8408
|
try {
|
|
8094
|
-
entries =
|
|
8409
|
+
entries = fs35.readdirSync(dir);
|
|
8095
8410
|
} catch {
|
|
8096
8411
|
continue;
|
|
8097
8412
|
}
|
|
8098
8413
|
const prefix = `${sprintId}_`;
|
|
8099
8414
|
for (const entry of entries) {
|
|
8100
8415
|
if ((entry.startsWith(prefix) || entry === `${sprintId}.md`) && entry.endsWith(".md")) {
|
|
8101
|
-
return
|
|
8416
|
+
return path37.join(dir, entry);
|
|
8102
8417
|
}
|
|
8103
8418
|
}
|
|
8104
8419
|
}
|
|
@@ -8106,26 +8421,26 @@ function findSprintFile(sprintId, cwd) {
|
|
|
8106
8421
|
}
|
|
8107
8422
|
function findWorkItemFileLocal(cwd, workItemId) {
|
|
8108
8423
|
const searchDirs = [
|
|
8109
|
-
|
|
8110
|
-
|
|
8424
|
+
path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
|
|
8425
|
+
path37.join(cwd, ".cleargate", "delivery", "archive")
|
|
8111
8426
|
];
|
|
8112
8427
|
const prefix = `${workItemId}_`;
|
|
8113
8428
|
for (const dir of searchDirs) {
|
|
8114
8429
|
let entries;
|
|
8115
8430
|
try {
|
|
8116
|
-
entries =
|
|
8431
|
+
entries = fs35.readdirSync(dir);
|
|
8117
8432
|
} catch {
|
|
8118
8433
|
continue;
|
|
8119
8434
|
}
|
|
8120
8435
|
const match = entries.find((e) => (e.startsWith(prefix) || e === `${workItemId}.md`) && e.endsWith(".md"));
|
|
8121
|
-
if (match) return
|
|
8436
|
+
if (match) return path37.join(dir, match);
|
|
8122
8437
|
}
|
|
8123
8438
|
return null;
|
|
8124
8439
|
}
|
|
8125
8440
|
function readCachedGateSync(absPath) {
|
|
8126
8441
|
let raw;
|
|
8127
8442
|
try {
|
|
8128
|
-
raw =
|
|
8443
|
+
raw = fs35.readFileSync(absPath, "utf8");
|
|
8129
8444
|
} catch {
|
|
8130
8445
|
return null;
|
|
8131
8446
|
}
|
|
@@ -8149,7 +8464,7 @@ function readCachedGateSync(absPath) {
|
|
|
8149
8464
|
return null;
|
|
8150
8465
|
}
|
|
8151
8466
|
function extractInScopeWorkItemIds(sprintFilePath, cwd, execFn) {
|
|
8152
|
-
const scriptPath =
|
|
8467
|
+
const scriptPath = path37.join(cwd, ".cleargate", "scripts", "assert_story_files.mjs");
|
|
8153
8468
|
const cmd = `node "${scriptPath}" "${sprintFilePath}" --emit-json`;
|
|
8154
8469
|
let stdout;
|
|
8155
8470
|
try {
|
|
@@ -8201,7 +8516,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
|
|
|
8201
8516
|
let fm;
|
|
8202
8517
|
let raw;
|
|
8203
8518
|
try {
|
|
8204
|
-
raw =
|
|
8519
|
+
raw = fs35.readFileSync(absPath, "utf8");
|
|
8205
8520
|
({ fm } = parseFrontmatter(raw));
|
|
8206
8521
|
} catch {
|
|
8207
8522
|
totalChecked++;
|
|
@@ -8245,7 +8560,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
|
|
|
8245
8560
|
let sprintRaw;
|
|
8246
8561
|
let sprintFm = {};
|
|
8247
8562
|
try {
|
|
8248
|
-
sprintRaw =
|
|
8563
|
+
sprintRaw = fs35.readFileSync(sprintFilePath, "utf8");
|
|
8249
8564
|
({ fm: sprintFm } = parseFrontmatter(sprintRaw));
|
|
8250
8565
|
} catch {
|
|
8251
8566
|
}
|
|
@@ -8301,13 +8616,13 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
|
|
|
8301
8616
|
if (!absPath) {
|
|
8302
8617
|
continue;
|
|
8303
8618
|
}
|
|
8304
|
-
if (absPath.includes(`${
|
|
8619
|
+
if (absPath.includes(`${path37.sep}archive${path37.sep}`)) {
|
|
8305
8620
|
result.skipped.push(id);
|
|
8306
8621
|
continue;
|
|
8307
8622
|
}
|
|
8308
8623
|
let status = "";
|
|
8309
8624
|
try {
|
|
8310
|
-
const raw =
|
|
8625
|
+
const raw = fs35.readFileSync(absPath, "utf8");
|
|
8311
8626
|
const { fm } = parseFrontmatter(raw);
|
|
8312
8627
|
status = String(fm["status"] ?? "");
|
|
8313
8628
|
} catch {
|
|
@@ -8380,8 +8695,8 @@ function sprintPreflightHandler(opts, cli) {
|
|
|
8380
8695
|
|
|
8381
8696
|
// src/commands/story.ts
|
|
8382
8697
|
init_cjs_shims();
|
|
8383
|
-
var
|
|
8384
|
-
var
|
|
8698
|
+
var fs36 = __toESM(require("fs"), 1);
|
|
8699
|
+
var path38 = __toESM(require("path"), 1);
|
|
8385
8700
|
var import_node_child_process12 = require("child_process");
|
|
8386
8701
|
function defaultExit2(code) {
|
|
8387
8702
|
return process.exit(code);
|
|
@@ -8389,7 +8704,7 @@ function defaultExit2(code) {
|
|
|
8389
8704
|
function resolveRunScript2(opts) {
|
|
8390
8705
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
8391
8706
|
const cwd = opts.cwd ?? process.cwd();
|
|
8392
|
-
return
|
|
8707
|
+
return path38.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
8393
8708
|
}
|
|
8394
8709
|
function deriveSprintBranch(sprintId) {
|
|
8395
8710
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -8398,11 +8713,11 @@ function deriveSprintBranch(sprintId) {
|
|
|
8398
8713
|
}
|
|
8399
8714
|
function atomicWriteString(filePath, text) {
|
|
8400
8715
|
const tmpFile = `${filePath}.tmp.${process.pid}`;
|
|
8401
|
-
|
|
8402
|
-
|
|
8716
|
+
fs36.writeFileSync(tmpFile, text, "utf8");
|
|
8717
|
+
fs36.renameSync(tmpFile, filePath);
|
|
8403
8718
|
}
|
|
8404
8719
|
function stateJsonPath(cwd, sprintId) {
|
|
8405
|
-
return
|
|
8720
|
+
return path38.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
|
|
8406
8721
|
}
|
|
8407
8722
|
function storyStartHandler(opts, cli) {
|
|
8408
8723
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -8419,7 +8734,7 @@ function storyStartHandler(opts, cli) {
|
|
|
8419
8734
|
return printInertAndExit(stdoutFn, exitFn);
|
|
8420
8735
|
}
|
|
8421
8736
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
8422
|
-
const worktreePath =
|
|
8737
|
+
const worktreePath = path38.join(cwd, ".worktrees", opts.storyId);
|
|
8423
8738
|
const storyBranch = `story/${opts.storyId}`;
|
|
8424
8739
|
const step1 = spawnFn(
|
|
8425
8740
|
"git",
|
|
@@ -8452,13 +8767,13 @@ function storyStartHandler(opts, cli) {
|
|
|
8452
8767
|
return exitFn(step2.status ?? 1);
|
|
8453
8768
|
}
|
|
8454
8769
|
const stateFile = stateJsonPath(cwd, sprintId);
|
|
8455
|
-
if (!
|
|
8770
|
+
if (!fs36.existsSync(stateFile)) {
|
|
8456
8771
|
stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
|
|
8457
8772
|
return exitFn(1);
|
|
8458
8773
|
}
|
|
8459
8774
|
let state2;
|
|
8460
8775
|
try {
|
|
8461
|
-
state2 = JSON.parse(
|
|
8776
|
+
state2 = JSON.parse(fs36.readFileSync(stateFile, "utf8"));
|
|
8462
8777
|
} catch (err) {
|
|
8463
8778
|
stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
|
|
8464
8779
|
return exitFn(1);
|
|
@@ -8499,7 +8814,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
8499
8814
|
}
|
|
8500
8815
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
8501
8816
|
const storyBranch = `story/${opts.storyId}`;
|
|
8502
|
-
const worktreeRel =
|
|
8817
|
+
const worktreeRel = path38.join(".worktrees", opts.storyId);
|
|
8503
8818
|
const step1 = spawnFn(
|
|
8504
8819
|
"git",
|
|
8505
8820
|
["rev-list", "--count", `${sprintBranch}..${storyBranch}`],
|
|
@@ -8598,7 +8913,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
8598
8913
|
|
|
8599
8914
|
// src/commands/state.ts
|
|
8600
8915
|
init_cjs_shims();
|
|
8601
|
-
var
|
|
8916
|
+
var path39 = __toESM(require("path"), 1);
|
|
8602
8917
|
var import_node_child_process13 = require("child_process");
|
|
8603
8918
|
function defaultExit3(code) {
|
|
8604
8919
|
return process.exit(code);
|
|
@@ -8606,7 +8921,7 @@ function defaultExit3(code) {
|
|
|
8606
8921
|
function resolveRunScript3(opts) {
|
|
8607
8922
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
8608
8923
|
const cwd = opts.cwd ?? process.cwd();
|
|
8609
|
-
return
|
|
8924
|
+
return path39.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
8610
8925
|
}
|
|
8611
8926
|
function stateUpdateHandler(opts, cli) {
|
|
8612
8927
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -8667,21 +8982,21 @@ function stateValidateHandler(opts, cli) {
|
|
|
8667
8982
|
|
|
8668
8983
|
// src/commands/stamp-tokens.ts
|
|
8669
8984
|
init_cjs_shims();
|
|
8670
|
-
var
|
|
8671
|
-
var
|
|
8985
|
+
var fs38 = __toESM(require("fs"), 1);
|
|
8986
|
+
var path41 = __toESM(require("path"), 1);
|
|
8672
8987
|
|
|
8673
8988
|
// src/lib/ledger-reader.ts
|
|
8674
8989
|
init_cjs_shims();
|
|
8675
|
-
var
|
|
8676
|
-
var
|
|
8990
|
+
var fs37 = __toESM(require("fs"), 1);
|
|
8991
|
+
var path40 = __toESM(require("path"), 1);
|
|
8677
8992
|
function findSprintRunsRoot(startDir) {
|
|
8678
8993
|
let dir = startDir;
|
|
8679
8994
|
while (true) {
|
|
8680
|
-
const candidate =
|
|
8681
|
-
if (
|
|
8995
|
+
const candidate = path40.join(dir, ".cleargate", "sprint-runs");
|
|
8996
|
+
if (fs37.existsSync(candidate)) {
|
|
8682
8997
|
return candidate;
|
|
8683
8998
|
}
|
|
8684
|
-
const parent =
|
|
8999
|
+
const parent = path40.dirname(dir);
|
|
8685
9000
|
if (parent === dir) {
|
|
8686
9001
|
return null;
|
|
8687
9002
|
}
|
|
@@ -8734,13 +9049,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
8734
9049
|
}
|
|
8735
9050
|
sprintRunsRoot = found;
|
|
8736
9051
|
}
|
|
8737
|
-
if (!
|
|
9052
|
+
if (!fs37.existsSync(sprintRunsRoot)) {
|
|
8738
9053
|
return [];
|
|
8739
9054
|
}
|
|
8740
9055
|
let ledgerFiles;
|
|
8741
9056
|
try {
|
|
8742
|
-
const entries =
|
|
8743
|
-
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));
|
|
8744
9059
|
} catch {
|
|
8745
9060
|
return [];
|
|
8746
9061
|
}
|
|
@@ -8748,7 +9063,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
8748
9063
|
for (const ledgerFile of ledgerFiles) {
|
|
8749
9064
|
let content;
|
|
8750
9065
|
try {
|
|
8751
|
-
content =
|
|
9066
|
+
content = fs37.readFileSync(ledgerFile, "utf-8");
|
|
8752
9067
|
} catch {
|
|
8753
9068
|
continue;
|
|
8754
9069
|
}
|
|
@@ -8799,7 +9114,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
8799
9114
|
});
|
|
8800
9115
|
const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
|
|
8801
9116
|
const cwd = cli?.cwd ?? process.cwd();
|
|
8802
|
-
const absPath =
|
|
9117
|
+
const absPath = path41.isAbsolute(file) ? file : path41.resolve(cwd, file);
|
|
8803
9118
|
if (/\/\.cleargate\/delivery\/archive\//.test(absPath)) {
|
|
8804
9119
|
stdoutFn(`[frozen] ${absPath}`);
|
|
8805
9120
|
exitFn(0);
|
|
@@ -8807,7 +9122,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
8807
9122
|
}
|
|
8808
9123
|
let rawContent;
|
|
8809
9124
|
try {
|
|
8810
|
-
rawContent =
|
|
9125
|
+
rawContent = fs38.readFileSync(absPath, "utf-8");
|
|
8811
9126
|
} catch {
|
|
8812
9127
|
stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
|
|
8813
9128
|
exitFn(1);
|
|
@@ -8879,7 +9194,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
8879
9194
|
return;
|
|
8880
9195
|
}
|
|
8881
9196
|
try {
|
|
8882
|
-
|
|
9197
|
+
fs38.writeFileSync(absPath, serialized, "utf-8");
|
|
8883
9198
|
} catch {
|
|
8884
9199
|
stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
|
|
8885
9200
|
exitFn(1);
|
|
@@ -8896,14 +9211,14 @@ function extractWorkItemId(fm, absPath) {
|
|
|
8896
9211
|
return val.trim();
|
|
8897
9212
|
}
|
|
8898
9213
|
}
|
|
8899
|
-
const
|
|
8900
|
-
const match =
|
|
9214
|
+
const basename14 = path41.basename(absPath);
|
|
9215
|
+
const match = basename14.match(/^(STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(-\d+)?/i);
|
|
8901
9216
|
if (match) {
|
|
8902
9217
|
return match[0].toUpperCase();
|
|
8903
9218
|
}
|
|
8904
9219
|
const typeFromPath = detectWorkItemType(absPath);
|
|
8905
9220
|
if (typeFromPath) {
|
|
8906
|
-
const idMatch =
|
|
9221
|
+
const idMatch = basename14.match(/((?:STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(?:-\d+)?)/i);
|
|
8907
9222
|
if (idMatch) {
|
|
8908
9223
|
return idMatch[1].toUpperCase();
|
|
8909
9224
|
}
|
|
@@ -9001,9 +9316,9 @@ ${body}`;
|
|
|
9001
9316
|
|
|
9002
9317
|
// src/commands/upgrade.ts
|
|
9003
9318
|
init_cjs_shims();
|
|
9004
|
-
var
|
|
9319
|
+
var fs39 = __toESM(require("fs"), 1);
|
|
9005
9320
|
var fsp = __toESM(require("fs/promises"), 1);
|
|
9006
|
-
var
|
|
9321
|
+
var path42 = __toESM(require("path"), 1);
|
|
9007
9322
|
|
|
9008
9323
|
// src/lib/changelog.ts
|
|
9009
9324
|
init_cjs_shims();
|
|
@@ -9207,7 +9522,7 @@ async function writeAtomic2(filePath, content) {
|
|
|
9207
9522
|
}
|
|
9208
9523
|
}
|
|
9209
9524
|
async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
9210
|
-
const snapshotPath =
|
|
9525
|
+
const snapshotPath = path42.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
9211
9526
|
let snapshot;
|
|
9212
9527
|
try {
|
|
9213
9528
|
const raw = await fsp.readFile(snapshotPath, "utf-8");
|
|
@@ -9224,17 +9539,17 @@ async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
|
9224
9539
|
await writeAtomic2(snapshotPath, JSON.stringify(updated, null, 2) + "\n");
|
|
9225
9540
|
}
|
|
9226
9541
|
function isClaudeMd(filePath) {
|
|
9227
|
-
return
|
|
9542
|
+
return path42.basename(filePath) === "CLAUDE.md";
|
|
9228
9543
|
}
|
|
9229
9544
|
function isSettingsJson(filePath) {
|
|
9230
|
-
return
|
|
9545
|
+
return path42.basename(filePath) === "settings.json" && filePath.includes(".claude");
|
|
9231
9546
|
}
|
|
9232
9547
|
async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
9233
|
-
const targetPath =
|
|
9234
|
-
const sourcePath =
|
|
9548
|
+
const targetPath = path42.join(projectRoot, entry.path);
|
|
9549
|
+
const sourcePath = path42.join(packageRoot, entry.path);
|
|
9235
9550
|
try {
|
|
9236
9551
|
const pkgContent = await fsp.readFile(sourcePath, "utf-8");
|
|
9237
|
-
await fsp.mkdir(
|
|
9552
|
+
await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
|
|
9238
9553
|
await writeAtomic2(targetPath, pkgContent);
|
|
9239
9554
|
await updateSnapshotEntry(projectRoot, entry.path, entry.sha256);
|
|
9240
9555
|
stdout(`[always] overwritten: ${entry.path}`);
|
|
@@ -9244,8 +9559,8 @@ async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
|
9244
9559
|
}
|
|
9245
9560
|
async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, currentSha, flags, opts) {
|
|
9246
9561
|
const { stdout, stderr, promptMergeChoiceFn, openInEditorFn, stdin } = opts;
|
|
9247
|
-
const targetPath =
|
|
9248
|
-
const sourcePath =
|
|
9562
|
+
const targetPath = path42.join(projectRoot, entry.path);
|
|
9563
|
+
const sourcePath = path42.join(packageRoot, entry.path);
|
|
9249
9564
|
let ours = "";
|
|
9250
9565
|
let theirs = "";
|
|
9251
9566
|
try {
|
|
@@ -9308,7 +9623,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
9308
9623
|
mergedContent = theirs;
|
|
9309
9624
|
}
|
|
9310
9625
|
}
|
|
9311
|
-
await fsp.mkdir(
|
|
9626
|
+
await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
|
|
9312
9627
|
await writeAtomic2(targetPath, mergedContent);
|
|
9313
9628
|
const newSha2 = hashNormalized(mergedContent);
|
|
9314
9629
|
await updateSnapshotEntry(projectRoot, entry.path, newSha2);
|
|
@@ -9320,7 +9635,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
9320
9635
|
${ours}=======
|
|
9321
9636
|
${theirs}>>>>>>> theirs (upstream)
|
|
9322
9637
|
`;
|
|
9323
|
-
await fsp.mkdir(
|
|
9638
|
+
await fsp.mkdir(path42.dirname(mergeFilePath), { recursive: true });
|
|
9324
9639
|
await writeAtomic2(mergeFilePath, conflictContent);
|
|
9325
9640
|
try {
|
|
9326
9641
|
const result = await openInEditorFn(mergeFilePath);
|
|
@@ -9380,10 +9695,10 @@ async function upgradeHandler(flags, cli) {
|
|
|
9380
9695
|
const installedVersion = installSnapshot?.cleargate_version ?? pkgManifest.cleargate_version;
|
|
9381
9696
|
const targetVersion = pkgManifest.cleargate_version;
|
|
9382
9697
|
if (installedVersion !== targetVersion) {
|
|
9383
|
-
const pkgRoot = cli?.packageRoot ??
|
|
9384
|
-
const changelogPath =
|
|
9698
|
+
const pkgRoot = cli?.packageRoot ?? path42.join(path42.dirname(new URL(importMetaUrl).pathname), "..", "..");
|
|
9699
|
+
const changelogPath = path42.join(pkgRoot, "CHANGELOG.md");
|
|
9385
9700
|
try {
|
|
9386
|
-
const changelogContent =
|
|
9701
|
+
const changelogContent = fs39.readFileSync(changelogPath, "utf-8");
|
|
9387
9702
|
const sections = sliceChangelog(changelogContent, installedVersion, targetVersion);
|
|
9388
9703
|
if (sections.length > 0) {
|
|
9389
9704
|
const deltaText = sections.map((s) => s.body).join("\n\n");
|
|
@@ -9449,7 +9764,7 @@ async function upgradeHandler(flags, cli) {
|
|
|
9449
9764
|
const { entry, currentSha, installSha, action } = item;
|
|
9450
9765
|
let preMutationContent = null;
|
|
9451
9766
|
if (SESSION_LOAD_PATHS.has(entry.path)) {
|
|
9452
|
-
const targetPath =
|
|
9767
|
+
const targetPath = path42.join(cwd, entry.path);
|
|
9453
9768
|
try {
|
|
9454
9769
|
preMutationContent = await fsp.readFile(targetPath, "utf-8");
|
|
9455
9770
|
} catch {
|
|
@@ -9487,7 +9802,7 @@ async function upgradeHandler(flags, cli) {
|
|
|
9487
9802
|
package_sha: entry.sha256
|
|
9488
9803
|
};
|
|
9489
9804
|
if (SESSION_LOAD_PATHS.has(entry.path) && preMutationContent !== null) {
|
|
9490
|
-
const targetPath =
|
|
9805
|
+
const targetPath = path42.join(cwd, entry.path);
|
|
9491
9806
|
let postMutationContent;
|
|
9492
9807
|
try {
|
|
9493
9808
|
postMutationContent = await fsp.readFile(targetPath, "utf-8");
|
|
@@ -9512,9 +9827,9 @@ async function upgradeHandler(flags, cli) {
|
|
|
9512
9827
|
|
|
9513
9828
|
// src/commands/uninstall.ts
|
|
9514
9829
|
init_cjs_shims();
|
|
9515
|
-
var
|
|
9830
|
+
var fs40 = __toESM(require("fs"), 1);
|
|
9516
9831
|
var fsp2 = __toESM(require("fs/promises"), 1);
|
|
9517
|
-
var
|
|
9832
|
+
var path43 = __toESM(require("path"), 1);
|
|
9518
9833
|
var import_node_child_process15 = require("child_process");
|
|
9519
9834
|
var USER_ARTIFACT_TIERS = ["user-artifact"];
|
|
9520
9835
|
var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
|
|
@@ -9540,10 +9855,10 @@ function shouldPreserve(entry, preserveSet, removeSet) {
|
|
|
9540
9855
|
return false;
|
|
9541
9856
|
}
|
|
9542
9857
|
function resolveProjectName(target) {
|
|
9543
|
-
const pkgPath =
|
|
9544
|
-
if (
|
|
9858
|
+
const pkgPath = path43.join(target, "package.json");
|
|
9859
|
+
if (fs40.existsSync(pkgPath)) {
|
|
9545
9860
|
try {
|
|
9546
|
-
const raw =
|
|
9861
|
+
const raw = fs40.readFileSync(pkgPath, "utf-8");
|
|
9547
9862
|
const parsed = JSON.parse(raw);
|
|
9548
9863
|
if (parsed.name && typeof parsed.name === "string") {
|
|
9549
9864
|
return parsed.name;
|
|
@@ -9551,7 +9866,7 @@ function resolveProjectName(target) {
|
|
|
9551
9866
|
} catch {
|
|
9552
9867
|
}
|
|
9553
9868
|
}
|
|
9554
|
-
return
|
|
9869
|
+
return path43.basename(target);
|
|
9555
9870
|
}
|
|
9556
9871
|
function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
9557
9872
|
const run = gitRunner ?? ((args) => {
|
|
@@ -9580,8 +9895,8 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
|
9580
9895
|
return changedFiles.filter((f) => manifestSet.has(f));
|
|
9581
9896
|
}
|
|
9582
9897
|
async function removeFromPackageJson(target, dryRun) {
|
|
9583
|
-
const pkgPath =
|
|
9584
|
-
if (!
|
|
9898
|
+
const pkgPath = path43.join(target, "package.json");
|
|
9899
|
+
if (!fs40.existsSync(pkgPath)) return false;
|
|
9585
9900
|
let raw;
|
|
9586
9901
|
try {
|
|
9587
9902
|
raw = await fsp2.readFile(pkgPath, "utf-8");
|
|
@@ -9622,7 +9937,7 @@ async function removeFile(filePath) {
|
|
|
9622
9937
|
}
|
|
9623
9938
|
async function removeDir(dirPath) {
|
|
9624
9939
|
try {
|
|
9625
|
-
|
|
9940
|
+
fs40.rmSync(dirPath, { recursive: true, force: true });
|
|
9626
9941
|
} catch {
|
|
9627
9942
|
}
|
|
9628
9943
|
}
|
|
@@ -9642,12 +9957,12 @@ async function uninstallHandler(opts) {
|
|
|
9642
9957
|
for (const t of FRAMEWORK_TIERS) removeSet.add(t);
|
|
9643
9958
|
for (const u of USER_ARTIFACT_TIERS) removeSet.add(u);
|
|
9644
9959
|
}
|
|
9645
|
-
const target = opts.path ?
|
|
9646
|
-
const cleargateDir =
|
|
9647
|
-
const manifestPath =
|
|
9648
|
-
const uninstalledPath =
|
|
9649
|
-
if (!
|
|
9650
|
-
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)) {
|
|
9651
9966
|
stdout("already uninstalled");
|
|
9652
9967
|
exit(0);
|
|
9653
9968
|
return;
|
|
@@ -9656,7 +9971,7 @@ async function uninstallHandler(opts) {
|
|
|
9656
9971
|
exit(0);
|
|
9657
9972
|
return;
|
|
9658
9973
|
}
|
|
9659
|
-
if (
|
|
9974
|
+
if (fs40.existsSync(uninstalledPath) && !fs40.existsSync(manifestPath)) {
|
|
9660
9975
|
stdout("already uninstalled");
|
|
9661
9976
|
exit(0);
|
|
9662
9977
|
return;
|
|
@@ -9678,10 +9993,10 @@ async function uninstallHandler(opts) {
|
|
|
9678
9993
|
return;
|
|
9679
9994
|
}
|
|
9680
9995
|
}
|
|
9681
|
-
const claudeMdPath =
|
|
9996
|
+
const claudeMdPath = path43.join(target, "CLAUDE.md");
|
|
9682
9997
|
let claudeMdContent = null;
|
|
9683
|
-
if (
|
|
9684
|
-
claudeMdContent =
|
|
9998
|
+
if (fs40.existsSync(claudeMdPath)) {
|
|
9999
|
+
claudeMdContent = fs40.readFileSync(claudeMdPath, "utf-8");
|
|
9685
10000
|
if (!claudeMdContent.includes(CLEARGATE_START)) {
|
|
9686
10001
|
stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
|
|
9687
10002
|
exit(1);
|
|
@@ -9697,8 +10012,8 @@ async function uninstallHandler(opts) {
|
|
|
9697
10012
|
const toPreserve = [];
|
|
9698
10013
|
const toSkip = [];
|
|
9699
10014
|
for (const entry of snapshot.files) {
|
|
9700
|
-
const filePath =
|
|
9701
|
-
if (!
|
|
10015
|
+
const filePath = path43.join(target, entry.path);
|
|
10016
|
+
if (!fs40.existsSync(filePath)) {
|
|
9702
10017
|
toSkip.push(entry);
|
|
9703
10018
|
continue;
|
|
9704
10019
|
}
|
|
@@ -9755,7 +10070,7 @@ async function uninstallHandler(opts) {
|
|
|
9755
10070
|
const removedPaths = [];
|
|
9756
10071
|
const preservedPaths = [];
|
|
9757
10072
|
for (const entry of toRemove) {
|
|
9758
|
-
const filePath =
|
|
10073
|
+
const filePath = path43.join(target, entry.path);
|
|
9759
10074
|
await removeFile(filePath);
|
|
9760
10075
|
removedPaths.push(entry.path);
|
|
9761
10076
|
}
|
|
@@ -9771,10 +10086,10 @@ async function uninstallHandler(opts) {
|
|
|
9771
10086
|
stderr(`Warning: could not strip CLAUDE.md block: ${err.message}`);
|
|
9772
10087
|
}
|
|
9773
10088
|
}
|
|
9774
|
-
const settingsPath =
|
|
9775
|
-
if (
|
|
10089
|
+
const settingsPath = path43.join(target, ".claude", "settings.json");
|
|
10090
|
+
if (fs40.existsSync(settingsPath)) {
|
|
9776
10091
|
try {
|
|
9777
|
-
const raw =
|
|
10092
|
+
const raw = fs40.readFileSync(settingsPath, "utf-8");
|
|
9778
10093
|
const settings = JSON.parse(raw);
|
|
9779
10094
|
const cleaned = removeClearGateHooks(settings);
|
|
9780
10095
|
await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
|
|
@@ -9789,7 +10104,7 @@ async function uninstallHandler(opts) {
|
|
|
9789
10104
|
stdout("Removed @cleargate/cli from package.json. Run `npm install` to update package-lock.json.");
|
|
9790
10105
|
}
|
|
9791
10106
|
await removeFile(manifestPath);
|
|
9792
|
-
await removeFile(
|
|
10107
|
+
await removeFile(path43.join(cleargateDir, ".drift-state.json"));
|
|
9793
10108
|
const marker = {
|
|
9794
10109
|
uninstalled_at: now().toISOString(),
|
|
9795
10110
|
prior_version: snapshot.cleargate_version,
|
|
@@ -9814,30 +10129,30 @@ async function uninstallHandler(opts) {
|
|
|
9814
10129
|
// src/commands/sync.ts
|
|
9815
10130
|
init_cjs_shims();
|
|
9816
10131
|
var fsPromises8 = __toESM(require("fs/promises"), 1);
|
|
9817
|
-
var
|
|
10132
|
+
var path52 = __toESM(require("path"), 1);
|
|
9818
10133
|
|
|
9819
10134
|
// src/lib/sync-log.ts
|
|
9820
10135
|
init_cjs_shims();
|
|
9821
|
-
var
|
|
10136
|
+
var fs41 = __toESM(require("fs"), 1);
|
|
9822
10137
|
var fsPromises2 = __toESM(require("fs/promises"), 1);
|
|
9823
|
-
var
|
|
10138
|
+
var path44 = __toESM(require("path"), 1);
|
|
9824
10139
|
function resolveActiveSprintDir(projectRoot, _opts) {
|
|
9825
|
-
const sprintRunsRoot =
|
|
9826
|
-
const offSprint =
|
|
9827
|
-
if (!
|
|
9828
|
-
|
|
9829
|
-
|
|
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 });
|
|
9830
10145
|
return offSprint;
|
|
9831
10146
|
}
|
|
9832
|
-
const entries =
|
|
10147
|
+
const entries = fs41.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
9833
10148
|
const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
|
|
9834
|
-
const fullPath =
|
|
9835
|
-
const stat =
|
|
10149
|
+
const fullPath = path44.join(sprintRunsRoot, e.name);
|
|
10150
|
+
const stat = fs41.statSync(fullPath);
|
|
9836
10151
|
return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
|
|
9837
10152
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
9838
10153
|
if (sprintDirs.length === 0) {
|
|
9839
|
-
if (!
|
|
9840
|
-
|
|
10154
|
+
if (!fs41.existsSync(offSprint)) {
|
|
10155
|
+
fs41.mkdirSync(offSprint, { recursive: true });
|
|
9841
10156
|
}
|
|
9842
10157
|
return offSprint;
|
|
9843
10158
|
}
|
|
@@ -9848,7 +10163,7 @@ function redactDetail(detail) {
|
|
|
9848
10163
|
return detail.replace(/eyJ[A-Za-z0-9._-]+/g, "[REDACTED]");
|
|
9849
10164
|
}
|
|
9850
10165
|
async function appendSyncLog(sprintRoot, entry) {
|
|
9851
|
-
const logPath =
|
|
10166
|
+
const logPath = path44.join(sprintRoot, "sync-log.jsonl");
|
|
9852
10167
|
await fsPromises2.mkdir(sprintRoot, { recursive: true });
|
|
9853
10168
|
const safeEntry = {
|
|
9854
10169
|
...entry,
|
|
@@ -9858,7 +10173,7 @@ async function appendSyncLog(sprintRoot, entry) {
|
|
|
9858
10173
|
await fsPromises2.appendFile(logPath, line, { encoding: "utf8" });
|
|
9859
10174
|
}
|
|
9860
10175
|
async function readSyncLog(sprintRoot, filters) {
|
|
9861
|
-
const logPath =
|
|
10176
|
+
const logPath = path44.join(sprintRoot, "sync-log.jsonl");
|
|
9862
10177
|
let raw;
|
|
9863
10178
|
try {
|
|
9864
10179
|
raw = await fsPromises2.readFile(logPath, "utf8");
|
|
@@ -9964,7 +10279,7 @@ function classify2(local, remote, since) {
|
|
|
9964
10279
|
init_cjs_shims();
|
|
9965
10280
|
var import_node_fs2 = require("fs");
|
|
9966
10281
|
var os8 = __toESM(require("os"), 1);
|
|
9967
|
-
var
|
|
10282
|
+
var path45 = __toESM(require("path"), 1);
|
|
9968
10283
|
function promptFourChoice(opts) {
|
|
9969
10284
|
const { stdin, stdout } = opts;
|
|
9970
10285
|
stdout("[k]eep mine / [t]ake theirs / [e]dit in $EDITOR / [a]bort: ");
|
|
@@ -10034,7 +10349,7 @@ async function promptThreeWayMerge(opts) {
|
|
|
10034
10349
|
case "a":
|
|
10035
10350
|
return { resolution: "aborted", body: local };
|
|
10036
10351
|
case "e": {
|
|
10037
|
-
const tmpFile =
|
|
10352
|
+
const tmpFile = path45.join(os8.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
|
|
10038
10353
|
const markerContent = `<<<<<<< local
|
|
10039
10354
|
${local}
|
|
10040
10355
|
=======
|
|
@@ -10134,12 +10449,12 @@ init_config();
|
|
|
10134
10449
|
// src/lib/intake.ts
|
|
10135
10450
|
init_cjs_shims();
|
|
10136
10451
|
var fsPromises4 = __toESM(require("fs/promises"), 1);
|
|
10137
|
-
var
|
|
10452
|
+
var path48 = __toESM(require("path"), 1);
|
|
10138
10453
|
|
|
10139
10454
|
// src/lib/slug.ts
|
|
10140
10455
|
init_cjs_shims();
|
|
10141
10456
|
var fsPromises3 = __toESM(require("fs/promises"), 1);
|
|
10142
|
-
var
|
|
10457
|
+
var path47 = __toESM(require("path"), 1);
|
|
10143
10458
|
function slugify(title, max = 40) {
|
|
10144
10459
|
const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
10145
10460
|
const lowered = normalized.toLowerCase();
|
|
@@ -10154,8 +10469,8 @@ function slugify(title, max = 40) {
|
|
|
10154
10469
|
var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
|
|
10155
10470
|
async function nextProposalId(projectRoot) {
|
|
10156
10471
|
const dirs = [
|
|
10157
|
-
|
|
10158
|
-
|
|
10472
|
+
path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
10473
|
+
path47.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
10159
10474
|
];
|
|
10160
10475
|
let maxN = 0;
|
|
10161
10476
|
for (const dir of dirs) {
|
|
@@ -10167,7 +10482,7 @@ async function nextProposalId(projectRoot) {
|
|
|
10167
10482
|
}
|
|
10168
10483
|
for (const entry of entries) {
|
|
10169
10484
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10170
|
-
const fullPath =
|
|
10485
|
+
const fullPath = path47.join(dir, entry.name);
|
|
10171
10486
|
try {
|
|
10172
10487
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
10173
10488
|
const fmEnd = extractFrontmatterBlock(raw);
|
|
@@ -10185,8 +10500,8 @@ async function nextProposalId(projectRoot) {
|
|
|
10185
10500
|
}
|
|
10186
10501
|
async function findByRemoteId(projectRoot, remoteId) {
|
|
10187
10502
|
const dirs = [
|
|
10188
|
-
|
|
10189
|
-
|
|
10503
|
+
path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
10504
|
+
path47.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
10190
10505
|
];
|
|
10191
10506
|
const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10192
10507
|
const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
|
|
@@ -10199,7 +10514,7 @@ async function findByRemoteId(projectRoot, remoteId) {
|
|
|
10199
10514
|
}
|
|
10200
10515
|
for (const entry of entries) {
|
|
10201
10516
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10202
|
-
const fullPath =
|
|
10517
|
+
const fullPath = path47.join(dir, entry.name);
|
|
10203
10518
|
try {
|
|
10204
10519
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
10205
10520
|
const fm = extractFrontmatterBlock(raw);
|
|
@@ -10236,7 +10551,7 @@ async function runIntakeBranch(opts) {
|
|
|
10236
10551
|
labelFilter = "cleargate:proposal",
|
|
10237
10552
|
now = () => (/* @__PURE__ */ new Date()).toISOString()
|
|
10238
10553
|
} = opts;
|
|
10239
|
-
const pendingSyncDir =
|
|
10554
|
+
const pendingSyncDir = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
10240
10555
|
let remoteItems = [];
|
|
10241
10556
|
try {
|
|
10242
10557
|
remoteItems = await mcp2.call(
|
|
@@ -10267,7 +10582,7 @@ async function runIntakeBranch(opts) {
|
|
|
10267
10582
|
const slug2 = slugify(item.title ?? "untitled");
|
|
10268
10583
|
const num2 = proposalId2.replace("PROP-", "");
|
|
10269
10584
|
const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
|
|
10270
|
-
const targetPath2 =
|
|
10585
|
+
const targetPath2 = path48.join(pendingSyncDir, filename2);
|
|
10271
10586
|
createdItems.push({
|
|
10272
10587
|
proposalId: proposalId2,
|
|
10273
10588
|
remoteId: item.remote_id,
|
|
@@ -10280,7 +10595,7 @@ async function runIntakeBranch(opts) {
|
|
|
10280
10595
|
const num = proposalId.replace("PROP-", "");
|
|
10281
10596
|
const slug = slugify(item.title ?? "untitled");
|
|
10282
10597
|
const filename = `PROPOSAL-${num}-remote-${slug}.md`;
|
|
10283
|
-
const targetPath =
|
|
10598
|
+
const targetPath = path48.join(pendingSyncDir, filename);
|
|
10284
10599
|
const nowTs = now();
|
|
10285
10600
|
const fm = {
|
|
10286
10601
|
proposal_id: proposalId,
|
|
@@ -10372,8 +10687,8 @@ path/to/new/file.ext - {Explanation of purpose}
|
|
|
10372
10687
|
}
|
|
10373
10688
|
async function hasAnyRemoteAuthored(projectRoot) {
|
|
10374
10689
|
const dirs = [
|
|
10375
|
-
|
|
10376
|
-
|
|
10690
|
+
path48.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
10691
|
+
path48.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
10377
10692
|
];
|
|
10378
10693
|
for (const dir of dirs) {
|
|
10379
10694
|
let entries;
|
|
@@ -10384,7 +10699,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
10384
10699
|
}
|
|
10385
10700
|
for (const entry of entries) {
|
|
10386
10701
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10387
|
-
const fullPath =
|
|
10702
|
+
const fullPath = path48.join(dir, entry.name);
|
|
10388
10703
|
try {
|
|
10389
10704
|
const raw = await fsPromises4.readFile(fullPath, "utf8");
|
|
10390
10705
|
const fmEnd = raw.indexOf("\n---", 4);
|
|
@@ -10402,9 +10717,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
10402
10717
|
|
|
10403
10718
|
// src/lib/active-criteria.ts
|
|
10404
10719
|
init_cjs_shims();
|
|
10405
|
-
var
|
|
10720
|
+
var fs44 = __toESM(require("fs"), 1);
|
|
10406
10721
|
var fsPromises5 = __toESM(require("fs/promises"), 1);
|
|
10407
|
-
var
|
|
10722
|
+
var path49 = __toESM(require("path"), 1);
|
|
10408
10723
|
async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
10409
10724
|
const active = /* @__PURE__ */ new Set();
|
|
10410
10725
|
const now = Date.parse(nowFn());
|
|
@@ -10429,7 +10744,7 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
10429
10744
|
const ids = /* @__PURE__ */ new Set();
|
|
10430
10745
|
try {
|
|
10431
10746
|
const sprintDir = resolveActiveSprintDir(projectRoot);
|
|
10432
|
-
const sprintId =
|
|
10747
|
+
const sprintId = path49.basename(sprintDir);
|
|
10433
10748
|
if (sprintId === "_off-sprint") return ids;
|
|
10434
10749
|
const sprintFile = await findSprintFile2(projectRoot, sprintId);
|
|
10435
10750
|
if (!sprintFile) return ids;
|
|
@@ -10444,14 +10759,14 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
10444
10759
|
return ids;
|
|
10445
10760
|
}
|
|
10446
10761
|
async function findSprintFile2(projectRoot, sprintId) {
|
|
10447
|
-
const pendingSync =
|
|
10448
|
-
const archive =
|
|
10762
|
+
const pendingSync = path49.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
10763
|
+
const archive = path49.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
10449
10764
|
for (const dir of [pendingSync, archive]) {
|
|
10450
10765
|
try {
|
|
10451
|
-
const entries =
|
|
10766
|
+
const entries = fs44.readdirSync(dir, { withFileTypes: true });
|
|
10452
10767
|
for (const entry of entries) {
|
|
10453
10768
|
if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
|
|
10454
|
-
return
|
|
10769
|
+
return path49.join(dir, entry.name);
|
|
10455
10770
|
}
|
|
10456
10771
|
}
|
|
10457
10772
|
} catch {
|
|
@@ -10463,12 +10778,12 @@ async function findSprintFile2(projectRoot, sprintId) {
|
|
|
10463
10778
|
// src/lib/comments-cache.ts
|
|
10464
10779
|
init_cjs_shims();
|
|
10465
10780
|
var fsPromises6 = __toESM(require("fs/promises"), 1);
|
|
10466
|
-
var
|
|
10781
|
+
var path50 = __toESM(require("path"), 1);
|
|
10467
10782
|
function cacheDir(projectRoot) {
|
|
10468
|
-
return
|
|
10783
|
+
return path50.join(projectRoot, ".cleargate", ".comments-cache");
|
|
10469
10784
|
}
|
|
10470
10785
|
function cachePath(projectRoot, remoteId) {
|
|
10471
|
-
return
|
|
10786
|
+
return path50.join(cacheDir(projectRoot), `${remoteId}.json`);
|
|
10472
10787
|
}
|
|
10473
10788
|
async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
10474
10789
|
const dir = cacheDir(projectRoot);
|
|
@@ -10483,7 +10798,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
|
10483
10798
|
// src/lib/wiki-comments-render.ts
|
|
10484
10799
|
init_cjs_shims();
|
|
10485
10800
|
var fsPromises7 = __toESM(require("fs/promises"), 1);
|
|
10486
|
-
var
|
|
10801
|
+
var path51 = __toESM(require("path"), 1);
|
|
10487
10802
|
var START = "<!-- cleargate:comments:start -->";
|
|
10488
10803
|
var END = "<!-- cleargate:comments:end -->";
|
|
10489
10804
|
function resolveBucket(fm) {
|
|
@@ -10528,7 +10843,7 @@ async function renderCommentsSection(opts) {
|
|
|
10528
10843
|
const bucket = resolveBucket(localItem.fm);
|
|
10529
10844
|
const primaryId = getPrimaryId(localItem.fm);
|
|
10530
10845
|
if (!bucket || !primaryId) return;
|
|
10531
|
-
const wikiPath =
|
|
10846
|
+
const wikiPath = path51.join(
|
|
10532
10847
|
projectRoot,
|
|
10533
10848
|
".cleargate",
|
|
10534
10849
|
"wiki",
|
|
@@ -10564,7 +10879,7 @@ async function renderCommentsSection(opts) {
|
|
|
10564
10879
|
await writeAtomic4(wikiPath, updated);
|
|
10565
10880
|
}
|
|
10566
10881
|
async function writeAtomic4(filePath, content) {
|
|
10567
|
-
await fsPromises7.mkdir(
|
|
10882
|
+
await fsPromises7.mkdir(path51.dirname(filePath), { recursive: true });
|
|
10568
10883
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
10569
10884
|
await fsPromises7.writeFile(tmpPath, content, "utf8");
|
|
10570
10885
|
await fsPromises7.rename(tmpPath, filePath);
|
|
@@ -10576,11 +10891,11 @@ async function syncCheckHandler(opts = {}) {
|
|
|
10576
10891
|
const env = opts.env ?? process.env;
|
|
10577
10892
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
10578
10893
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
10579
|
-
const markerPath =
|
|
10894
|
+
const markerPath = path52.join(projectRoot, ".cleargate", ".sync-marker.json");
|
|
10580
10895
|
const updateMarker = async (nowIso2) => {
|
|
10581
10896
|
try {
|
|
10582
10897
|
const content = JSON.stringify({ last_check: nowIso2 });
|
|
10583
|
-
await fsPromises8.mkdir(
|
|
10898
|
+
await fsPromises8.mkdir(path52.dirname(markerPath), { recursive: true });
|
|
10584
10899
|
const tmpPath = `${markerPath}.tmp.${Date.now()}`;
|
|
10585
10900
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
10586
10901
|
await fsPromises8.rename(tmpPath, markerPath);
|
|
@@ -10663,7 +10978,7 @@ async function syncHandler(opts = {}) {
|
|
|
10663
10978
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
10664
10979
|
const identity = resolveIdentity(projectRoot);
|
|
10665
10980
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
10666
|
-
const sprintId =
|
|
10981
|
+
const sprintId = path52.basename(sprintRoot);
|
|
10667
10982
|
let mcp2;
|
|
10668
10983
|
if (opts.mcp) {
|
|
10669
10984
|
mcp2 = opts.mcp;
|
|
@@ -10724,7 +11039,7 @@ async function syncHandler(opts = {}) {
|
|
|
10724
11039
|
exit(2);
|
|
10725
11040
|
return;
|
|
10726
11041
|
}
|
|
10727
|
-
const wikiMetaPath =
|
|
11042
|
+
const wikiMetaPath = path52.join(projectRoot, ".cleargate", "wiki", "meta.json");
|
|
10728
11043
|
let lastRemoteSync = "1970-01-01T00:00:00.000Z";
|
|
10729
11044
|
try {
|
|
10730
11045
|
const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -10965,7 +11280,7 @@ async function syncHandler(opts = {}) {
|
|
|
10965
11280
|
};
|
|
10966
11281
|
await appendSyncLog(sprintRoot, entry);
|
|
10967
11282
|
}
|
|
10968
|
-
const conflictsFile =
|
|
11283
|
+
const conflictsFile = path52.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
10969
11284
|
const conflictsContent = {
|
|
10970
11285
|
generated_at: nowFn(),
|
|
10971
11286
|
sprint_id: sprintId,
|
|
@@ -10973,7 +11288,7 @@ async function syncHandler(opts = {}) {
|
|
|
10973
11288
|
};
|
|
10974
11289
|
await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
|
|
10975
11290
|
try {
|
|
10976
|
-
await fsPromises8.mkdir(
|
|
11291
|
+
await fsPromises8.mkdir(path52.dirname(wikiMetaPath), { recursive: true });
|
|
10977
11292
|
let meta = {};
|
|
10978
11293
|
try {
|
|
10979
11294
|
const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -11014,13 +11329,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
|
|
|
11014
11329
|
await writeAtomic5(localPath, newContent);
|
|
11015
11330
|
}
|
|
11016
11331
|
async function writeAtomic5(filePath, content) {
|
|
11017
|
-
await fsPromises8.mkdir(
|
|
11332
|
+
await fsPromises8.mkdir(path52.dirname(filePath), { recursive: true });
|
|
11018
11333
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11019
11334
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
11020
11335
|
await fsPromises8.rename(tmpPath, filePath);
|
|
11021
11336
|
}
|
|
11022
11337
|
async function scanLocalItems(projectRoot) {
|
|
11023
|
-
const pendingSync =
|
|
11338
|
+
const pendingSync = path52.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
11024
11339
|
const results = [];
|
|
11025
11340
|
let entries;
|
|
11026
11341
|
try {
|
|
@@ -11030,7 +11345,7 @@ async function scanLocalItems(projectRoot) {
|
|
|
11030
11345
|
}
|
|
11031
11346
|
for (const entry of entries) {
|
|
11032
11347
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11033
|
-
const fullPath =
|
|
11348
|
+
const fullPath = path52.join(pendingSync, entry.name);
|
|
11034
11349
|
try {
|
|
11035
11350
|
const raw = await fsPromises8.readFile(fullPath, "utf8");
|
|
11036
11351
|
const { fm, body } = parseFrontmatter(raw);
|
|
@@ -11058,7 +11373,7 @@ init_config();
|
|
|
11058
11373
|
// src/lib/sync/work-items.ts
|
|
11059
11374
|
init_cjs_shims();
|
|
11060
11375
|
var fsPromises9 = __toESM(require("fs/promises"), 1);
|
|
11061
|
-
var
|
|
11376
|
+
var path53 = __toESM(require("path"), 1);
|
|
11062
11377
|
var import_node_crypto2 = require("crypto");
|
|
11063
11378
|
var BATCH_SIZE = 100;
|
|
11064
11379
|
var ATTRIBUTION_FIELDS = /* @__PURE__ */ new Set([
|
|
@@ -11106,8 +11421,8 @@ function getItemId2(fm) {
|
|
|
11106
11421
|
}
|
|
11107
11422
|
async function walkDeliveryDirs(projectRoot) {
|
|
11108
11423
|
const dirs = [
|
|
11109
|
-
|
|
11110
|
-
|
|
11424
|
+
path53.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
11425
|
+
path53.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
11111
11426
|
];
|
|
11112
11427
|
const results = [];
|
|
11113
11428
|
for (const dir of dirs) {
|
|
@@ -11119,7 +11434,7 @@ async function walkDeliveryDirs(projectRoot) {
|
|
|
11119
11434
|
}
|
|
11120
11435
|
for (const entry of entries) {
|
|
11121
11436
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11122
|
-
const fullPath =
|
|
11437
|
+
const fullPath = path53.join(dir, entry.name);
|
|
11123
11438
|
try {
|
|
11124
11439
|
const raw = await fsPromises9.readFile(fullPath, "utf8");
|
|
11125
11440
|
const { fm, body } = parseFrontmatter(raw);
|
|
@@ -11144,7 +11459,7 @@ async function walkDeliveryDirs(projectRoot) {
|
|
|
11144
11459
|
return results;
|
|
11145
11460
|
}
|
|
11146
11461
|
async function writeAtomic6(filePath, content) {
|
|
11147
|
-
await fsPromises9.mkdir(
|
|
11462
|
+
await fsPromises9.mkdir(path53.dirname(filePath), { recursive: true });
|
|
11148
11463
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11149
11464
|
await fsPromises9.writeFile(tmpPath, content, "utf8");
|
|
11150
11465
|
await fsPromises9.rename(tmpPath, filePath);
|
|
@@ -11211,9 +11526,9 @@ async function syncWorkItems(opts) {
|
|
|
11211
11526
|
|
|
11212
11527
|
// src/lib/admin-url.ts
|
|
11213
11528
|
init_cjs_shims();
|
|
11214
|
-
var
|
|
11529
|
+
var fs45 = __toESM(require("fs"), 1);
|
|
11215
11530
|
var os10 = __toESM(require("os"), 1);
|
|
11216
|
-
var
|
|
11531
|
+
var path54 = __toESM(require("path"), 1);
|
|
11217
11532
|
var DEFAULT_BASE = "https://admin.cleargate.soula.ge/";
|
|
11218
11533
|
function adminUrl(urlPath, opts) {
|
|
11219
11534
|
const env = opts?.env ?? process.env;
|
|
@@ -11236,8 +11551,8 @@ function adminUrl(urlPath, opts) {
|
|
|
11236
11551
|
function readLocalConfig() {
|
|
11237
11552
|
const home = os10.homedir();
|
|
11238
11553
|
if (!home) return null;
|
|
11239
|
-
const configPath =
|
|
11240
|
-
const raw =
|
|
11554
|
+
const configPath = path54.join(home, ".cleargate", "config.json");
|
|
11555
|
+
const raw = fs45.readFileSync(configPath, "utf8");
|
|
11241
11556
|
return JSON.parse(raw);
|
|
11242
11557
|
}
|
|
11243
11558
|
|
|
@@ -11352,7 +11667,7 @@ async function syncWorkItemsHandler(opts = {}) {
|
|
|
11352
11667
|
// src/commands/pull.ts
|
|
11353
11668
|
init_cjs_shims();
|
|
11354
11669
|
var fsPromises10 = __toESM(require("fs/promises"), 1);
|
|
11355
|
-
var
|
|
11670
|
+
var path55 = __toESM(require("path"), 1);
|
|
11356
11671
|
init_acquire();
|
|
11357
11672
|
init_config();
|
|
11358
11673
|
async function pullHandler(idOrRemoteId, opts = {}) {
|
|
@@ -11467,7 +11782,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
11467
11782
|
result: "ok"
|
|
11468
11783
|
};
|
|
11469
11784
|
await appendSyncLog(sprintRoot, entry);
|
|
11470
|
-
stdout(`pull: ${remoteId} applied to ${
|
|
11785
|
+
stdout(`pull: ${remoteId} applied to ${path55.relative(projectRoot, localPath)}
|
|
11471
11786
|
`);
|
|
11472
11787
|
if (opts.comments) {
|
|
11473
11788
|
const comments = await mcp2.call(
|
|
@@ -11490,7 +11805,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
11490
11805
|
if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
|
|
11491
11806
|
return idOrRemoteId;
|
|
11492
11807
|
}
|
|
11493
|
-
const pendingSync =
|
|
11808
|
+
const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
11494
11809
|
let entries;
|
|
11495
11810
|
try {
|
|
11496
11811
|
entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -11500,7 +11815,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
11500
11815
|
for (const entry of entries) {
|
|
11501
11816
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11502
11817
|
try {
|
|
11503
|
-
const raw = await fsPromises10.readFile(
|
|
11818
|
+
const raw = await fsPromises10.readFile(path55.join(pendingSync, entry.name), "utf8");
|
|
11504
11819
|
const { fm } = parseFrontmatter(raw);
|
|
11505
11820
|
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
11506
11821
|
if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
|
|
@@ -11513,7 +11828,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
11513
11828
|
return null;
|
|
11514
11829
|
}
|
|
11515
11830
|
async function findLocalFile(remoteId, projectRoot) {
|
|
11516
|
-
const pendingSync =
|
|
11831
|
+
const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
11517
11832
|
let entries;
|
|
11518
11833
|
try {
|
|
11519
11834
|
entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -11522,7 +11837,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
11522
11837
|
}
|
|
11523
11838
|
for (const entry of entries) {
|
|
11524
11839
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11525
|
-
const fullPath =
|
|
11840
|
+
const fullPath = path55.join(pendingSync, entry.name);
|
|
11526
11841
|
try {
|
|
11527
11842
|
const raw = await fsPromises10.readFile(fullPath, "utf8");
|
|
11528
11843
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -11533,7 +11848,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
11533
11848
|
return null;
|
|
11534
11849
|
}
|
|
11535
11850
|
async function writeAtomic7(filePath, content) {
|
|
11536
|
-
await fsPromises10.mkdir(
|
|
11851
|
+
await fsPromises10.mkdir(path55.dirname(filePath), { recursive: true });
|
|
11537
11852
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11538
11853
|
await fsPromises10.writeFile(tmpPath, content, "utf8");
|
|
11539
11854
|
await fsPromises10.rename(tmpPath, filePath);
|
|
@@ -11548,8 +11863,9 @@ function getItemId3(fm) {
|
|
|
11548
11863
|
|
|
11549
11864
|
// src/commands/push.ts
|
|
11550
11865
|
init_cjs_shims();
|
|
11866
|
+
var fs46 = __toESM(require("fs"), 1);
|
|
11551
11867
|
var fsPromises11 = __toESM(require("fs/promises"), 1);
|
|
11552
|
-
var
|
|
11868
|
+
var path56 = __toESM(require("path"), 1);
|
|
11553
11869
|
init_acquire();
|
|
11554
11870
|
init_config();
|
|
11555
11871
|
async function pushHandler(fileOrId, opts = {}) {
|
|
@@ -11559,6 +11875,13 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
11559
11875
|
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
11560
11876
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
11561
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
|
+
}
|
|
11562
11885
|
const identity = resolveIdentity(projectRoot);
|
|
11563
11886
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
11564
11887
|
async function resolveMcp() {
|
|
@@ -11625,7 +11948,7 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
11625
11948
|
}
|
|
11626
11949
|
async function handlePush(filePath, ctx) {
|
|
11627
11950
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
11628
|
-
const resolvedPath =
|
|
11951
|
+
const resolvedPath = path56.isAbsolute(filePath) ? filePath : path56.resolve(projectRoot, filePath);
|
|
11629
11952
|
if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
|
|
11630
11953
|
if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
|
|
11631
11954
|
stderr(
|
|
@@ -11767,8 +12090,8 @@ async function handleRevert(idOrRemoteId, ctx) {
|
|
|
11767
12090
|
void localPath;
|
|
11768
12091
|
}
|
|
11769
12092
|
async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
11770
|
-
const pendingSync =
|
|
11771
|
-
const archive =
|
|
12093
|
+
const pendingSync = path56.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
12094
|
+
const archive = path56.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
11772
12095
|
for (const dir of [pendingSync, archive]) {
|
|
11773
12096
|
let entries;
|
|
11774
12097
|
try {
|
|
@@ -11778,7 +12101,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
11778
12101
|
}
|
|
11779
12102
|
for (const entry of entries) {
|
|
11780
12103
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11781
|
-
const fullPath =
|
|
12104
|
+
const fullPath = path56.join(dir, entry.name);
|
|
11782
12105
|
try {
|
|
11783
12106
|
const raw = await fsPromises11.readFile(fullPath, "utf8");
|
|
11784
12107
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -11797,7 +12120,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
11797
12120
|
return null;
|
|
11798
12121
|
}
|
|
11799
12122
|
async function writeAtomic8(filePath, content) {
|
|
11800
|
-
await fsPromises11.mkdir(
|
|
12123
|
+
await fsPromises11.mkdir(path56.dirname(filePath), { recursive: true });
|
|
11801
12124
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11802
12125
|
await fsPromises11.writeFile(tmpPath, content, "utf8");
|
|
11803
12126
|
await fsPromises11.rename(tmpPath, filePath);
|
|
@@ -11833,7 +12156,7 @@ function getItemTypeWithPathOverride(localPath, fm) {
|
|
|
11833
12156
|
// src/commands/conflicts.ts
|
|
11834
12157
|
init_cjs_shims();
|
|
11835
12158
|
var fsPromises12 = __toESM(require("fs/promises"), 1);
|
|
11836
|
-
var
|
|
12159
|
+
var path57 = __toESM(require("path"), 1);
|
|
11837
12160
|
init_acquire();
|
|
11838
12161
|
init_config();
|
|
11839
12162
|
var RESOLUTION_HINTS = {
|
|
@@ -11871,7 +12194,7 @@ async function conflictsHandler(opts = {}) {
|
|
|
11871
12194
|
}
|
|
11872
12195
|
}
|
|
11873
12196
|
}
|
|
11874
|
-
const conflictsFile =
|
|
12197
|
+
const conflictsFile = path57.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
11875
12198
|
let data;
|
|
11876
12199
|
try {
|
|
11877
12200
|
const raw = await fsPromises12.readFile(conflictsFile, "utf8");
|
|
@@ -11959,8 +12282,8 @@ function formatEntry(entry) {
|
|
|
11959
12282
|
|
|
11960
12283
|
// src/commands/admin-login.ts
|
|
11961
12284
|
init_cjs_shims();
|
|
11962
|
-
var
|
|
11963
|
-
var
|
|
12285
|
+
var fs47 = __toESM(require("fs"), 1);
|
|
12286
|
+
var path58 = __toESM(require("path"), 1);
|
|
11964
12287
|
var os11 = __toESM(require("os"), 1);
|
|
11965
12288
|
var DEFAULT_MCP_URL = "http://localhost:3000";
|
|
11966
12289
|
function resolveMcpUrl(mcpUrlFlag, env) {
|
|
@@ -11969,14 +12292,14 @@ function resolveMcpUrl(mcpUrlFlag, env) {
|
|
|
11969
12292
|
function resolveAuthFilePath(opts) {
|
|
11970
12293
|
if (opts.authFilePath) return opts.authFilePath;
|
|
11971
12294
|
const homedirFn = opts.homedir ?? os11.homedir;
|
|
11972
|
-
return
|
|
12295
|
+
return path58.join(homedirFn(), ".cleargate", "admin-auth.json");
|
|
11973
12296
|
}
|
|
11974
12297
|
function writeAdminAuth(filePath, token) {
|
|
11975
|
-
const dir =
|
|
11976
|
-
|
|
12298
|
+
const dir = path58.dirname(filePath);
|
|
12299
|
+
fs47.mkdirSync(dir, { recursive: true });
|
|
11977
12300
|
const payload = JSON.stringify({ version: 1, token }, null, 2);
|
|
11978
|
-
|
|
11979
|
-
|
|
12301
|
+
fs47.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
|
|
12302
|
+
fs47.chmodSync(filePath, 384);
|
|
11980
12303
|
}
|
|
11981
12304
|
async function adminLoginHandler(opts = {}) {
|
|
11982
12305
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
@@ -12086,8 +12409,8 @@ async function adminLoginHandler(opts = {}) {
|
|
|
12086
12409
|
|
|
12087
12410
|
// src/commands/hotfix.ts
|
|
12088
12411
|
init_cjs_shims();
|
|
12089
|
-
var
|
|
12090
|
-
var
|
|
12412
|
+
var fs48 = __toESM(require("fs"), 1);
|
|
12413
|
+
var path59 = __toESM(require("path"), 1);
|
|
12091
12414
|
function defaultExit4(code) {
|
|
12092
12415
|
return process.exit(code);
|
|
12093
12416
|
}
|
|
@@ -12097,7 +12420,7 @@ function maxHotfixId(pendingDir) {
|
|
|
12097
12420
|
let max = 0;
|
|
12098
12421
|
let entries;
|
|
12099
12422
|
try {
|
|
12100
|
-
entries =
|
|
12423
|
+
entries = fs48.readdirSync(pendingDir);
|
|
12101
12424
|
} catch {
|
|
12102
12425
|
return 0;
|
|
12103
12426
|
}
|
|
@@ -12111,13 +12434,13 @@ function maxHotfixId(pendingDir) {
|
|
|
12111
12434
|
return max;
|
|
12112
12435
|
}
|
|
12113
12436
|
function countActiveHotfixes(repoRoot) {
|
|
12114
|
-
const pendingDir =
|
|
12115
|
-
const archiveDir =
|
|
12437
|
+
const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
12438
|
+
const archiveDir = path59.join(repoRoot, ".cleargate", "delivery", "archive");
|
|
12116
12439
|
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
12117
12440
|
let count = 0;
|
|
12118
12441
|
let pendingEntries = [];
|
|
12119
12442
|
try {
|
|
12120
|
-
pendingEntries =
|
|
12443
|
+
pendingEntries = fs48.readdirSync(pendingDir);
|
|
12121
12444
|
} catch {
|
|
12122
12445
|
}
|
|
12123
12446
|
for (const entry of pendingEntries) {
|
|
@@ -12125,13 +12448,13 @@ function countActiveHotfixes(repoRoot) {
|
|
|
12125
12448
|
}
|
|
12126
12449
|
let archiveEntries = [];
|
|
12127
12450
|
try {
|
|
12128
|
-
archiveEntries =
|
|
12451
|
+
archiveEntries = fs48.readdirSync(archiveDir);
|
|
12129
12452
|
} catch {
|
|
12130
12453
|
}
|
|
12131
12454
|
for (const entry of archiveEntries) {
|
|
12132
12455
|
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
|
|
12133
12456
|
try {
|
|
12134
|
-
const stat =
|
|
12457
|
+
const stat = fs48.statSync(path59.join(archiveDir, entry));
|
|
12135
12458
|
if (stat.mtimeMs >= sevenDaysAgo) count++;
|
|
12136
12459
|
} catch {
|
|
12137
12460
|
}
|
|
@@ -12140,7 +12463,7 @@ function countActiveHotfixes(repoRoot) {
|
|
|
12140
12463
|
return count;
|
|
12141
12464
|
}
|
|
12142
12465
|
function resolveTemplatePath(repoRoot) {
|
|
12143
|
-
return
|
|
12466
|
+
return path59.join(repoRoot, ".cleargate", "templates", "hotfix.md");
|
|
12144
12467
|
}
|
|
12145
12468
|
function hotfixNewHandler(opts, cli) {
|
|
12146
12469
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -12159,14 +12482,14 @@ function hotfixNewHandler(opts, cli) {
|
|
|
12159
12482
|
);
|
|
12160
12483
|
return exitFn(1);
|
|
12161
12484
|
}
|
|
12162
|
-
const pendingDir =
|
|
12485
|
+
const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
12163
12486
|
const maxId = maxHotfixId(pendingDir);
|
|
12164
12487
|
const nextId = maxId + 1;
|
|
12165
12488
|
const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
|
|
12166
12489
|
const templatePath = resolveTemplatePath(repoRoot);
|
|
12167
12490
|
let templateContent;
|
|
12168
12491
|
try {
|
|
12169
|
-
templateContent =
|
|
12492
|
+
templateContent = fs48.readFileSync(templatePath, "utf8");
|
|
12170
12493
|
} catch {
|
|
12171
12494
|
stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
|
|
12172
12495
|
return exitFn(2);
|
|
@@ -12174,10 +12497,10 @@ function hotfixNewHandler(opts, cli) {
|
|
|
12174
12497
|
const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
|
|
12175
12498
|
const fileSlug = opts.slug.replace(/-/g, "_");
|
|
12176
12499
|
const fileName = `${idStr}_${fileSlug}.md`;
|
|
12177
|
-
const outPath =
|
|
12500
|
+
const outPath = path59.join(pendingDir, fileName);
|
|
12178
12501
|
try {
|
|
12179
|
-
|
|
12180
|
-
|
|
12502
|
+
fs48.mkdirSync(pendingDir, { recursive: true });
|
|
12503
|
+
fs48.writeFileSync(outPath, content, "utf8");
|
|
12181
12504
|
} catch (err) {
|
|
12182
12505
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12183
12506
|
stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
|
|
@@ -12362,7 +12685,7 @@ async function mcpServeHandler(opts) {
|
|
|
12362
12685
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12363
12686
|
stderr(`cleargate mcp serve: proxy error: ${errMsg}
|
|
12364
12687
|
`);
|
|
12365
|
-
const id =
|
|
12688
|
+
const id = extractId2(line);
|
|
12366
12689
|
if (id !== void 0) {
|
|
12367
12690
|
stdout(
|
|
12368
12691
|
JSON.stringify({
|
|
@@ -12438,7 +12761,7 @@ async function streamSse(res, stdout) {
|
|
|
12438
12761
|
}
|
|
12439
12762
|
}
|
|
12440
12763
|
}
|
|
12441
|
-
function
|
|
12764
|
+
function extractId2(line) {
|
|
12442
12765
|
try {
|
|
12443
12766
|
const obj = JSON.parse(line);
|
|
12444
12767
|
return "id" in obj ? obj.id : void 0;
|
|
@@ -12610,8 +12933,8 @@ sprint.command("close <sprint-id>").description("close a sprint \u2014 validates
|
|
|
12610
12933
|
}
|
|
12611
12934
|
sprintCloseHandler(handlerOpts);
|
|
12612
12935
|
});
|
|
12613
|
-
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) => {
|
|
12614
|
-
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 });
|
|
12615
12938
|
});
|
|
12616
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) => {
|
|
12617
12940
|
const handlerOpts = { sprintId };
|