cleargate 0.6.1 → 0.7.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 +2 -2
- package/dist/cli.cjs +526 -460
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +535 -470
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +2 -2
- package/package.json +1 -1
- package/templates/cleargate-planning/MANIFEST.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { Command } from "commander";
|
|
|
14
14
|
// package.json
|
|
15
15
|
var package_default = {
|
|
16
16
|
name: "cleargate",
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "0.7.0",
|
|
18
18
|
private: false,
|
|
19
19
|
type: "module",
|
|
20
20
|
description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
|
|
@@ -1272,8 +1272,8 @@ async function stampHandler(file, opts, cli) {
|
|
|
1272
1272
|
}
|
|
1273
1273
|
|
|
1274
1274
|
// src/commands/init.ts
|
|
1275
|
-
import * as
|
|
1276
|
-
import * as
|
|
1275
|
+
import * as fs14 from "fs";
|
|
1276
|
+
import * as path14 from "path";
|
|
1277
1277
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1278
1278
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1279
1279
|
|
|
@@ -1285,6 +1285,7 @@ var HOOK_FILES_WITH_PIN = /* @__PURE__ */ new Set([
|
|
|
1285
1285
|
".claude/hooks/stamp-and-gate.sh",
|
|
1286
1286
|
".claude/hooks/session-start.sh"
|
|
1287
1287
|
]);
|
|
1288
|
+
var SKIP_FILES = /* @__PURE__ */ new Set(["CLAUDE.md"]);
|
|
1288
1289
|
function listFilesRecursive(dir) {
|
|
1289
1290
|
const results = [];
|
|
1290
1291
|
function walk(current, rel) {
|
|
@@ -1307,7 +1308,7 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1307
1308
|
if (!fs5.existsSync(payloadDir)) {
|
|
1308
1309
|
throw new Error(`copyPayload: payloadDir does not exist: ${payloadDir}`);
|
|
1309
1310
|
}
|
|
1310
|
-
const files = listFilesRecursive(payloadDir);
|
|
1311
|
+
const files = listFilesRecursive(payloadDir).filter((r) => !SKIP_FILES.has(r));
|
|
1311
1312
|
for (const relPath of files) {
|
|
1312
1313
|
const srcPath = path4.join(payloadDir, relPath);
|
|
1313
1314
|
const dstPath = path4.join(targetCwd, relPath);
|
|
@@ -1318,9 +1319,14 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1318
1319
|
srcContent = text;
|
|
1319
1320
|
}
|
|
1320
1321
|
const srcBuffer = typeof srcContent === "string" ? Buffer.from(srcContent, "utf8") : srcContent;
|
|
1322
|
+
const srcMode = fs5.statSync(srcPath).mode;
|
|
1323
|
+
const needsExec = (srcMode & 73) !== 0 || relPath.endsWith(".sh");
|
|
1321
1324
|
if (fs5.existsSync(dstPath)) {
|
|
1322
1325
|
const dstContent = fs5.readFileSync(dstPath);
|
|
1323
1326
|
if (srcBuffer.equals(dstContent)) {
|
|
1327
|
+
if (needsExec && process.platform !== "win32") {
|
|
1328
|
+
fs5.chmodSync(dstPath, 493);
|
|
1329
|
+
}
|
|
1324
1330
|
report.skipped++;
|
|
1325
1331
|
report.actions.push({ action: "skipped", relPath });
|
|
1326
1332
|
continue;
|
|
@@ -1331,10 +1337,16 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1331
1337
|
continue;
|
|
1332
1338
|
}
|
|
1333
1339
|
fs5.writeFileSync(dstPath, srcBuffer);
|
|
1340
|
+
if (needsExec && process.platform !== "win32") {
|
|
1341
|
+
fs5.chmodSync(dstPath, 493);
|
|
1342
|
+
}
|
|
1334
1343
|
report.overwritten++;
|
|
1335
1344
|
report.actions.push({ action: "overwritten", relPath });
|
|
1336
1345
|
} else {
|
|
1337
1346
|
fs5.writeFileSync(dstPath, srcBuffer);
|
|
1347
|
+
if (needsExec && process.platform !== "win32") {
|
|
1348
|
+
fs5.chmodSync(dstPath, 493);
|
|
1349
|
+
}
|
|
1338
1350
|
report.created++;
|
|
1339
1351
|
report.actions.push({ action: "created", relPath });
|
|
1340
1352
|
}
|
|
@@ -1401,13 +1413,46 @@ function injectClaudeMd(existing, block) {
|
|
|
1401
1413
|
return existing.trimEnd() + "\n\n" + block + "\n";
|
|
1402
1414
|
}
|
|
1403
1415
|
|
|
1416
|
+
// src/init/inject-mcp-json.ts
|
|
1417
|
+
import * as fs6 from "fs";
|
|
1418
|
+
import * as path5 from "path";
|
|
1419
|
+
function mergeMcpJson(existing, entry) {
|
|
1420
|
+
const next = existing ? { ...existing } : {};
|
|
1421
|
+
const servers = { ...next.mcpServers ?? {} };
|
|
1422
|
+
servers.cleargate = entry;
|
|
1423
|
+
next.mcpServers = servers;
|
|
1424
|
+
return JSON.stringify(next, null, 2) + "\n";
|
|
1425
|
+
}
|
|
1426
|
+
function injectMcpJson(cwd, url) {
|
|
1427
|
+
const dst = path5.join(cwd, ".mcp.json");
|
|
1428
|
+
const entry = { type: "http", url };
|
|
1429
|
+
let existing = null;
|
|
1430
|
+
let existingRaw = null;
|
|
1431
|
+
if (fs6.existsSync(dst)) {
|
|
1432
|
+
existingRaw = fs6.readFileSync(dst, "utf8");
|
|
1433
|
+
try {
|
|
1434
|
+
existing = JSON.parse(existingRaw);
|
|
1435
|
+
} catch {
|
|
1436
|
+
throw new Error(
|
|
1437
|
+
`inject-mcp-json: ${dst} is not valid JSON; refusing to overwrite. Fix or remove the file and re-run init.`
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const next = mergeMcpJson(existing, entry);
|
|
1442
|
+
if (existingRaw !== null && existingRaw === next) {
|
|
1443
|
+
return "unchanged";
|
|
1444
|
+
}
|
|
1445
|
+
fs6.writeFileSync(dst, next);
|
|
1446
|
+
return existingRaw === null ? "created" : "updated";
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1404
1449
|
// src/commands/wiki-build.ts
|
|
1405
|
-
import * as
|
|
1406
|
-
import * as
|
|
1450
|
+
import * as fs12 from "fs";
|
|
1451
|
+
import * as path11 from "path";
|
|
1407
1452
|
|
|
1408
1453
|
// src/wiki/scan.ts
|
|
1409
|
-
import * as
|
|
1410
|
-
import * as
|
|
1454
|
+
import * as fs7 from "fs";
|
|
1455
|
+
import * as path6 from "path";
|
|
1411
1456
|
|
|
1412
1457
|
// src/wiki/derive-bucket.ts
|
|
1413
1458
|
var PREFIX_MAP = [
|
|
@@ -1450,19 +1495,19 @@ var EXCLUDED_SUFFIXES = [
|
|
|
1450
1495
|
function scanRawItems(deliveryRoot, repoRoot) {
|
|
1451
1496
|
const results = [];
|
|
1452
1497
|
for (const subdir of ["pending-sync", "archive"]) {
|
|
1453
|
-
const dir =
|
|
1454
|
-
if (!
|
|
1455
|
-
const entries =
|
|
1498
|
+
const dir = path6.join(deliveryRoot, subdir);
|
|
1499
|
+
if (!fs7.existsSync(dir)) continue;
|
|
1500
|
+
const entries = fs7.readdirSync(dir, { recursive: true, encoding: "utf8" });
|
|
1456
1501
|
for (const rel of entries) {
|
|
1457
1502
|
if (!rel.endsWith(".md")) continue;
|
|
1458
1503
|
if (rel.includes("~") || rel.startsWith(".")) continue;
|
|
1459
|
-
const absPath =
|
|
1460
|
-
const stat =
|
|
1504
|
+
const absPath = path6.join(dir, rel);
|
|
1505
|
+
const stat = fs7.statSync(absPath);
|
|
1461
1506
|
if (!stat.isFile()) continue;
|
|
1462
|
-
const rawPath =
|
|
1507
|
+
const rawPath = path6.relative(repoRoot, absPath).replace(/\\/g, "/");
|
|
1463
1508
|
const isExcluded = EXCLUDED_SUFFIXES.some((excl) => rawPath.startsWith(excl));
|
|
1464
1509
|
if (isExcluded) continue;
|
|
1465
|
-
const filename =
|
|
1510
|
+
const filename = path6.basename(absPath);
|
|
1466
1511
|
let bucketInfo;
|
|
1467
1512
|
try {
|
|
1468
1513
|
bucketInfo = deriveBucket(filename);
|
|
@@ -1475,7 +1520,7 @@ function scanRawItems(deliveryRoot, repoRoot) {
|
|
|
1475
1520
|
} catch {
|
|
1476
1521
|
continue;
|
|
1477
1522
|
}
|
|
1478
|
-
const raw =
|
|
1523
|
+
const raw = fs7.readFileSync(absPath, "utf8");
|
|
1479
1524
|
let fm;
|
|
1480
1525
|
let body;
|
|
1481
1526
|
try {
|
|
@@ -1587,8 +1632,8 @@ function parseFmRaw(raw) {
|
|
|
1587
1632
|
}
|
|
1588
1633
|
|
|
1589
1634
|
// src/wiki/synthesis/active-sprint.ts
|
|
1590
|
-
import * as
|
|
1591
|
-
import * as
|
|
1635
|
+
import * as fs8 from "fs";
|
|
1636
|
+
import * as path7 from "path";
|
|
1592
1637
|
import { fileURLToPath } from "url";
|
|
1593
1638
|
|
|
1594
1639
|
// src/wiki/synthesis/render.ts
|
|
@@ -1627,7 +1672,7 @@ function renderSection(template, ctx) {
|
|
|
1627
1672
|
// src/wiki/synthesis/active-sprint.ts
|
|
1628
1673
|
function compile(state2, templateDir) {
|
|
1629
1674
|
const tplDir = templateDir ?? resolveDefaultTemplateDir();
|
|
1630
|
-
const tpl =
|
|
1675
|
+
const tpl = fs8.readFileSync(path7.join(tplDir, "active-sprint.md"), "utf8");
|
|
1631
1676
|
const sprints = state2.filter((i) => i.bucket === "sprints");
|
|
1632
1677
|
const active = sprints.filter((s) => isSet(s.fm["activated_at"]) && !isSet(s.fm["completed_at"]));
|
|
1633
1678
|
const completed = sprints.filter((s) => isSet(s.fm["completed_at"]));
|
|
@@ -1651,17 +1696,17 @@ function isSet(val) {
|
|
|
1651
1696
|
return s !== "" && s !== "null";
|
|
1652
1697
|
}
|
|
1653
1698
|
function resolveDefaultTemplateDir() {
|
|
1654
|
-
const __dirname2 =
|
|
1655
|
-
return
|
|
1699
|
+
const __dirname2 = path7.dirname(fileURLToPath(import.meta.url));
|
|
1700
|
+
return path7.resolve(__dirname2, "..", "templates", "synthesis");
|
|
1656
1701
|
}
|
|
1657
1702
|
|
|
1658
1703
|
// src/wiki/synthesis/open-gates.ts
|
|
1659
|
-
import * as
|
|
1660
|
-
import * as
|
|
1704
|
+
import * as fs9 from "fs";
|
|
1705
|
+
import * as path8 from "path";
|
|
1661
1706
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1662
1707
|
function compile2(state2, templateDir) {
|
|
1663
1708
|
const tplDir = templateDir ?? resolveDefaultTemplateDir2();
|
|
1664
|
-
const tpl =
|
|
1709
|
+
const tpl = fs9.readFileSync(path8.join(tplDir, "open-gates.md"), "utf8");
|
|
1665
1710
|
const gate1 = state2.filter((i) => {
|
|
1666
1711
|
if (i.bucket !== "proposals") return false;
|
|
1667
1712
|
const status = String(i.fm["status"] ?? "");
|
|
@@ -1690,17 +1735,17 @@ function compile2(state2, templateDir) {
|
|
|
1690
1735
|
return renderTemplate(tpl, data);
|
|
1691
1736
|
}
|
|
1692
1737
|
function resolveDefaultTemplateDir2() {
|
|
1693
|
-
const __dirname2 =
|
|
1694
|
-
return
|
|
1738
|
+
const __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
1739
|
+
return path8.resolve(__dirname2, "..", "templates", "synthesis");
|
|
1695
1740
|
}
|
|
1696
1741
|
|
|
1697
1742
|
// src/wiki/synthesis/product-state.ts
|
|
1698
|
-
import * as
|
|
1699
|
-
import * as
|
|
1743
|
+
import * as fs10 from "fs";
|
|
1744
|
+
import * as path9 from "path";
|
|
1700
1745
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1701
1746
|
function compile3(state2, templateDir) {
|
|
1702
1747
|
const tplDir = templateDir ?? resolveDefaultTemplateDir3();
|
|
1703
|
-
const tpl =
|
|
1748
|
+
const tpl = fs10.readFileSync(path9.join(tplDir, "product-state.md"), "utf8");
|
|
1704
1749
|
function countBucket(bucket) {
|
|
1705
1750
|
return state2.filter((i) => i.bucket === bucket);
|
|
1706
1751
|
}
|
|
@@ -1747,17 +1792,17 @@ function compile3(state2, templateDir) {
|
|
|
1747
1792
|
return renderTemplate(tpl, data);
|
|
1748
1793
|
}
|
|
1749
1794
|
function resolveDefaultTemplateDir3() {
|
|
1750
|
-
const __dirname2 =
|
|
1751
|
-
return
|
|
1795
|
+
const __dirname2 = path9.dirname(fileURLToPath3(import.meta.url));
|
|
1796
|
+
return path9.resolve(__dirname2, "..", "templates", "synthesis");
|
|
1752
1797
|
}
|
|
1753
1798
|
|
|
1754
1799
|
// src/wiki/synthesis/roadmap.ts
|
|
1755
|
-
import * as
|
|
1756
|
-
import * as
|
|
1800
|
+
import * as fs11 from "fs";
|
|
1801
|
+
import * as path10 from "path";
|
|
1757
1802
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1758
1803
|
function compile4(state2, templateDir) {
|
|
1759
1804
|
const tplDir = templateDir ?? resolveDefaultTemplateDir4();
|
|
1760
|
-
const tpl =
|
|
1805
|
+
const tpl = fs11.readFileSync(path10.join(tplDir, "roadmap.md"), "utf8");
|
|
1761
1806
|
const sprints = state2.filter((i) => i.bucket === "sprints");
|
|
1762
1807
|
const epics = state2.filter((i) => i.bucket === "epics");
|
|
1763
1808
|
const inFlightSprints = sprints.filter(
|
|
@@ -1810,8 +1855,8 @@ function isShippedStatus(status) {
|
|
|
1810
1855
|
return status === "Completed" || status === "Approved";
|
|
1811
1856
|
}
|
|
1812
1857
|
function resolveDefaultTemplateDir4() {
|
|
1813
|
-
const __dirname2 =
|
|
1814
|
-
return
|
|
1858
|
+
const __dirname2 = path10.dirname(fileURLToPath4(import.meta.url));
|
|
1859
|
+
return path10.resolve(__dirname2, "..", "templates", "synthesis");
|
|
1815
1860
|
}
|
|
1816
1861
|
|
|
1817
1862
|
// src/commands/wiki-build.ts
|
|
@@ -1836,16 +1881,16 @@ async function wikiBuildHandler(opts = {}) {
|
|
|
1836
1881
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
1837
1882
|
const gitRunner = opts.gitRunner;
|
|
1838
1883
|
const templateDir = opts.templateDir;
|
|
1839
|
-
const deliveryRoot =
|
|
1840
|
-
const wikiRoot =
|
|
1841
|
-
if (!
|
|
1884
|
+
const deliveryRoot = path11.join(cwd, ".cleargate", "delivery");
|
|
1885
|
+
const wikiRoot = path11.join(cwd, ".cleargate", "wiki");
|
|
1886
|
+
if (!fs12.existsSync(deliveryRoot)) {
|
|
1842
1887
|
stderr(`wiki build: .cleargate/delivery/ not found at ${deliveryRoot}
|
|
1843
1888
|
`);
|
|
1844
1889
|
exit(1);
|
|
1845
1890
|
return;
|
|
1846
1891
|
}
|
|
1847
1892
|
for (const bucket of BUCKET_ORDER) {
|
|
1848
|
-
|
|
1893
|
+
fs12.mkdirSync(path11.join(wikiRoot, bucket), { recursive: true });
|
|
1849
1894
|
}
|
|
1850
1895
|
const items = scanRawItems(deliveryRoot, cwd);
|
|
1851
1896
|
const timestamp = now();
|
|
@@ -1868,19 +1913,19 @@ async function wikiBuildHandler(opts = {}) {
|
|
|
1868
1913
|
};
|
|
1869
1914
|
const body = buildPageBody(item, wikiPage);
|
|
1870
1915
|
const content = serializePage(wikiPage, body);
|
|
1871
|
-
const pageDir =
|
|
1872
|
-
|
|
1873
|
-
|
|
1916
|
+
const pageDir = path11.join(wikiRoot, item.bucket);
|
|
1917
|
+
fs12.mkdirSync(pageDir, { recursive: true });
|
|
1918
|
+
fs12.writeFileSync(path11.join(pageDir, `${item.id}.md`), content, "utf8");
|
|
1874
1919
|
pagesWritten++;
|
|
1875
1920
|
}
|
|
1876
1921
|
const indexContent = buildIndex(items);
|
|
1877
|
-
|
|
1922
|
+
fs12.writeFileSync(path11.join(wikiRoot, "index.md"), indexContent, "utf8");
|
|
1878
1923
|
const logContent = buildLog(items, timestamp);
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1924
|
+
fs12.writeFileSync(path11.join(wikiRoot, "log.md"), logContent, "utf8");
|
|
1925
|
+
fs12.writeFileSync(path11.join(wikiRoot, "active-sprint.md"), compile(items, templateDir), "utf8");
|
|
1926
|
+
fs12.writeFileSync(path11.join(wikiRoot, "open-gates.md"), compile2(items, templateDir), "utf8");
|
|
1927
|
+
fs12.writeFileSync(path11.join(wikiRoot, "product-state.md"), compile3(items, templateDir), "utf8");
|
|
1928
|
+
fs12.writeFileSync(path11.join(wikiRoot, "roadmap.md"), compile4(items, templateDir), "utf8");
|
|
1884
1929
|
stdout(`wiki build: OK (${pagesWritten} pages written)
|
|
1885
1930
|
`);
|
|
1886
1931
|
}
|
|
@@ -2045,8 +2090,8 @@ function buildLog(items, timestamp) {
|
|
|
2045
2090
|
|
|
2046
2091
|
// src/lib/manifest.ts
|
|
2047
2092
|
import { readFile as readFile3, writeFile as writeFile2, rename, mkdir } from "fs/promises";
|
|
2048
|
-
import { existsSync as
|
|
2049
|
-
import * as
|
|
2093
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
2094
|
+
import * as path12 from "path";
|
|
2050
2095
|
|
|
2051
2096
|
// src/lib/sha256.ts
|
|
2052
2097
|
import { createHash } from "crypto";
|
|
@@ -2069,31 +2114,31 @@ function shortHash(full) {
|
|
|
2069
2114
|
// src/lib/manifest.ts
|
|
2070
2115
|
function resolveDefaultPackageRoot() {
|
|
2071
2116
|
const here = new URL(".", import.meta.url).pathname;
|
|
2072
|
-
const distCandidate =
|
|
2073
|
-
if (
|
|
2117
|
+
const distCandidate = path12.join(here, "MANIFEST.json");
|
|
2118
|
+
if (existsSync8(distCandidate)) {
|
|
2074
2119
|
return here;
|
|
2075
2120
|
}
|
|
2076
|
-
const oneLevelUp =
|
|
2077
|
-
if (
|
|
2078
|
-
return
|
|
2121
|
+
const oneLevelUp = path12.join(here, "..", "MANIFEST.json");
|
|
2122
|
+
if (existsSync8(oneLevelUp)) {
|
|
2123
|
+
return path12.join(here, "..");
|
|
2079
2124
|
}
|
|
2080
|
-
const devCandidate =
|
|
2081
|
-
if (
|
|
2082
|
-
return
|
|
2125
|
+
const devCandidate = path12.join(here, "..", "..", "..", "cleargate-planning", "MANIFEST.json");
|
|
2126
|
+
if (existsSync8(devCandidate)) {
|
|
2127
|
+
return path12.join(here, "..", "..", "..", "cleargate-planning");
|
|
2083
2128
|
}
|
|
2084
2129
|
return here;
|
|
2085
2130
|
}
|
|
2086
2131
|
function loadPackageManifest(opts) {
|
|
2087
2132
|
const packageRoot = opts?.packageRoot ?? resolveDefaultPackageRoot();
|
|
2088
|
-
const manifestPath =
|
|
2089
|
-
if (!
|
|
2133
|
+
const manifestPath = path12.join(packageRoot, "MANIFEST.json");
|
|
2134
|
+
if (!existsSync8(manifestPath)) {
|
|
2090
2135
|
throw new Error(
|
|
2091
2136
|
`MANIFEST.json not found at ${manifestPath}; run 'npm run build' to generate it.`
|
|
2092
2137
|
);
|
|
2093
2138
|
}
|
|
2094
2139
|
let raw;
|
|
2095
2140
|
try {
|
|
2096
|
-
raw =
|
|
2141
|
+
raw = readFileSync11(manifestPath, "utf-8");
|
|
2097
2142
|
} catch {
|
|
2098
2143
|
throw new Error(
|
|
2099
2144
|
`MANIFEST.json not found at ${manifestPath}; run 'npm run build' to generate it.`
|
|
@@ -2102,7 +2147,7 @@ function loadPackageManifest(opts) {
|
|
|
2102
2147
|
return JSON.parse(raw);
|
|
2103
2148
|
}
|
|
2104
2149
|
async function loadInstallSnapshot(projectRoot) {
|
|
2105
|
-
const snapshotPath =
|
|
2150
|
+
const snapshotPath = path12.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
2106
2151
|
try {
|
|
2107
2152
|
const raw = await readFile3(snapshotPath, "utf-8");
|
|
2108
2153
|
return JSON.parse(raw);
|
|
@@ -2111,7 +2156,7 @@ async function loadInstallSnapshot(projectRoot) {
|
|
|
2111
2156
|
}
|
|
2112
2157
|
}
|
|
2113
2158
|
async function computeCurrentSha(file, projectRoot) {
|
|
2114
|
-
const filePath =
|
|
2159
|
+
const filePath = path12.join(projectRoot, file.path);
|
|
2115
2160
|
try {
|
|
2116
2161
|
const raw = await readFile3(filePath);
|
|
2117
2162
|
return hashNormalized(raw);
|
|
@@ -2140,8 +2185,8 @@ function classify(pkgSha, installSha, currentSha, tier) {
|
|
|
2140
2185
|
return "both-changed";
|
|
2141
2186
|
}
|
|
2142
2187
|
async function writeDriftState(projectRoot, state2, opts) {
|
|
2143
|
-
const cleargatDir =
|
|
2144
|
-
const finalPath =
|
|
2188
|
+
const cleargatDir = path12.join(projectRoot, ".cleargate");
|
|
2189
|
+
const finalPath = path12.join(cleargatDir, ".drift-state.json");
|
|
2145
2190
|
const tmpPath = `${finalPath}.tmp`;
|
|
2146
2191
|
const lastRefreshed = opts?.lastRefreshed ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2147
2192
|
const fileContent = { last_refreshed: lastRefreshed, drift: state2 };
|
|
@@ -2150,7 +2195,7 @@ async function writeDriftState(projectRoot, state2, opts) {
|
|
|
2150
2195
|
await rename(tmpPath, finalPath);
|
|
2151
2196
|
}
|
|
2152
2197
|
async function readDriftState(projectRoot) {
|
|
2153
|
-
const driftPath =
|
|
2198
|
+
const driftPath = path12.join(projectRoot, ".cleargate", ".drift-state.json");
|
|
2154
2199
|
try {
|
|
2155
2200
|
const raw = await readFile3(driftPath, "utf-8");
|
|
2156
2201
|
const parsed = JSON.parse(raw);
|
|
@@ -2223,24 +2268,24 @@ async function promptEmail(question, defaultValue, opts) {
|
|
|
2223
2268
|
}
|
|
2224
2269
|
|
|
2225
2270
|
// src/lib/identity.ts
|
|
2226
|
-
import * as
|
|
2227
|
-
import * as
|
|
2271
|
+
import * as fs13 from "fs";
|
|
2272
|
+
import * as path13 from "path";
|
|
2228
2273
|
import * as fsPromises from "fs/promises";
|
|
2229
2274
|
import * as os3 from "os";
|
|
2230
2275
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2231
2276
|
function readParticipant(projectRoot) {
|
|
2232
|
-
const filePath =
|
|
2277
|
+
const filePath = path13.join(projectRoot, ".cleargate", ".participant.json");
|
|
2233
2278
|
try {
|
|
2234
|
-
const raw =
|
|
2279
|
+
const raw = fs13.readFileSync(filePath, "utf8");
|
|
2235
2280
|
return JSON.parse(raw);
|
|
2236
2281
|
} catch {
|
|
2237
2282
|
return null;
|
|
2238
2283
|
}
|
|
2239
2284
|
}
|
|
2240
2285
|
async function writeParticipant(projectRoot, email, source, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2241
|
-
const cleargateDir =
|
|
2286
|
+
const cleargateDir = path13.join(projectRoot, ".cleargate");
|
|
2242
2287
|
await fsPromises.mkdir(cleargateDir, { recursive: true });
|
|
2243
|
-
const filePath =
|
|
2288
|
+
const filePath = path13.join(cleargateDir, ".participant.json");
|
|
2244
2289
|
const tmpPath = filePath + ".tmp." + Date.now();
|
|
2245
2290
|
const content = {
|
|
2246
2291
|
email,
|
|
@@ -2316,16 +2361,16 @@ var HOOK_ADDITION = {
|
|
|
2316
2361
|
};
|
|
2317
2362
|
function resolveDefaultPayloadDir() {
|
|
2318
2363
|
const thisFile = fileURLToPath5(import.meta.url);
|
|
2319
|
-
const pkgRoot =
|
|
2320
|
-
return
|
|
2364
|
+
const pkgRoot = path14.resolve(path14.dirname(thisFile), "..");
|
|
2365
|
+
return path14.join(pkgRoot, "templates", "cleargate-planning");
|
|
2321
2366
|
}
|
|
2322
2367
|
function countDeliveryItems(cwd) {
|
|
2323
|
-
const pendingSync =
|
|
2324
|
-
const archive =
|
|
2368
|
+
const pendingSync = path14.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
2369
|
+
const archive = path14.join(cwd, ".cleargate", "delivery", "archive");
|
|
2325
2370
|
let count = 0;
|
|
2326
2371
|
for (const dir of [pendingSync, archive]) {
|
|
2327
|
-
if (!
|
|
2328
|
-
const entries =
|
|
2372
|
+
if (!fs14.existsSync(dir)) continue;
|
|
2373
|
+
const entries = fs14.readdirSync(dir);
|
|
2329
2374
|
for (const f of entries) {
|
|
2330
2375
|
if (f.endsWith(".md") && f !== ".gitkeep") count++;
|
|
2331
2376
|
}
|
|
@@ -2334,12 +2379,12 @@ function countDeliveryItems(cwd) {
|
|
|
2334
2379
|
}
|
|
2335
2380
|
function writeAtomic(filePath, content) {
|
|
2336
2381
|
const tmpPath = filePath + ".tmp." + Date.now();
|
|
2337
|
-
|
|
2338
|
-
|
|
2382
|
+
fs14.writeFileSync(tmpPath, content, "utf8");
|
|
2383
|
+
fs14.renameSync(tmpPath, filePath);
|
|
2339
2384
|
}
|
|
2340
2385
|
function readPackageVersion(packageJsonPath) {
|
|
2341
2386
|
try {
|
|
2342
|
-
const raw =
|
|
2387
|
+
const raw = fs14.readFileSync(packageJsonPath, "utf8");
|
|
2343
2388
|
const pkg = JSON.parse(raw);
|
|
2344
2389
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
2345
2390
|
return pkg.version;
|
|
@@ -2359,16 +2404,16 @@ async function initHandler(opts = {}) {
|
|
|
2359
2404
|
const promptYesNoFn = opts.promptYesNo ?? promptYesNo;
|
|
2360
2405
|
const promptEmailFn = opts.promptEmail ?? promptEmail;
|
|
2361
2406
|
const spawnSyncFn = opts.spawnSyncFn ?? spawnSync3;
|
|
2362
|
-
if (!
|
|
2407
|
+
if (!fs14.existsSync(cwd)) {
|
|
2363
2408
|
stderr(`[cleargate init] ERROR: target directory does not exist: ${cwd}
|
|
2364
2409
|
`);
|
|
2365
2410
|
exit(1);
|
|
2366
2411
|
return;
|
|
2367
2412
|
}
|
|
2368
|
-
const testWritePath =
|
|
2413
|
+
const testWritePath = path14.join(cwd, `.cleargate-init-write-test-${Date.now()}`);
|
|
2369
2414
|
try {
|
|
2370
|
-
|
|
2371
|
-
|
|
2415
|
+
fs14.writeFileSync(testWritePath, "");
|
|
2416
|
+
fs14.unlinkSync(testWritePath);
|
|
2372
2417
|
} catch {
|
|
2373
2418
|
stderr(`[cleargate init] ERROR: target directory is not writable: ${cwd}
|
|
2374
2419
|
`);
|
|
@@ -2378,7 +2423,7 @@ async function initHandler(opts = {}) {
|
|
|
2378
2423
|
stdout(`[cleargate init] Target: ${cwd}
|
|
2379
2424
|
`);
|
|
2380
2425
|
const payloadDir = opts.payloadDir ?? resolveDefaultPayloadDir();
|
|
2381
|
-
if (!
|
|
2426
|
+
if (!fs14.existsSync(payloadDir)) {
|
|
2382
2427
|
stderr(`[cleargate init] ERROR: payload directory not found: ${payloadDir}
|
|
2383
2428
|
`);
|
|
2384
2429
|
stderr(`[cleargate init] Run \`npm run prebuild\` to copy the payload first.
|
|
@@ -2386,12 +2431,12 @@ async function initHandler(opts = {}) {
|
|
|
2386
2431
|
exit(1);
|
|
2387
2432
|
return;
|
|
2388
2433
|
}
|
|
2389
|
-
const uninstalledMarkerPath =
|
|
2434
|
+
const uninstalledMarkerPath = path14.join(cwd, ".cleargate", ".uninstalled");
|
|
2390
2435
|
let uninstalledMarker = null;
|
|
2391
2436
|
let userChoseRestore = false;
|
|
2392
|
-
if (
|
|
2437
|
+
if (fs14.existsSync(uninstalledMarkerPath)) {
|
|
2393
2438
|
try {
|
|
2394
|
-
const raw =
|
|
2439
|
+
const raw = fs14.readFileSync(uninstalledMarkerPath, "utf8");
|
|
2395
2440
|
uninstalledMarker = JSON.parse(raw);
|
|
2396
2441
|
} catch {
|
|
2397
2442
|
stderr(`[cleargate init] WARNING: .uninstalled marker is malformed; ignoring it.
|
|
@@ -2403,8 +2448,8 @@ async function initHandler(opts = {}) {
|
|
|
2403
2448
|
userChoseRestore = await promptYesNoFn(question, true);
|
|
2404
2449
|
if (userChoseRestore) {
|
|
2405
2450
|
for (const preservedPath of preserved) {
|
|
2406
|
-
const absPreserved =
|
|
2407
|
-
if (
|
|
2451
|
+
const absPreserved = path14.isAbsolute(preservedPath) ? preservedPath : path14.join(cwd, preservedPath);
|
|
2452
|
+
if (fs14.existsSync(absPreserved)) {
|
|
2408
2453
|
stdout(`[cleargate init] [preserved] ${preservedPath}
|
|
2409
2454
|
`);
|
|
2410
2455
|
} else {
|
|
@@ -2424,8 +2469,8 @@ async function initHandler(opts = {}) {
|
|
|
2424
2469
|
if (opts.pin) {
|
|
2425
2470
|
pinVersion = opts.pin;
|
|
2426
2471
|
} else {
|
|
2427
|
-
const payloadParent =
|
|
2428
|
-
pinVersion = readPackageVersion(
|
|
2472
|
+
const payloadParent = path14.resolve(payloadDir, "..", "..");
|
|
2473
|
+
pinVersion = readPackageVersion(path14.join(payloadParent, "package.json")) ?? readPackageVersion(path14.join(path14.dirname(fileURLToPath5(import.meta.url)), "..", "package.json")) ?? "latest";
|
|
2429
2474
|
}
|
|
2430
2475
|
const copyReport = copyPayload(payloadDir, cwd, { force, pinVersion });
|
|
2431
2476
|
for (const action of copyReport.actions) {
|
|
@@ -2433,33 +2478,33 @@ async function initHandler(opts = {}) {
|
|
|
2433
2478
|
stdout(`[cleargate init] ${verb} ${action.relPath}
|
|
2434
2479
|
`);
|
|
2435
2480
|
}
|
|
2436
|
-
const settingsPath =
|
|
2481
|
+
const settingsPath = path14.join(cwd, ".claude", "settings.json");
|
|
2437
2482
|
let existingSettings = null;
|
|
2438
|
-
if (
|
|
2483
|
+
if (fs14.existsSync(settingsPath)) {
|
|
2439
2484
|
try {
|
|
2440
|
-
existingSettings = JSON.parse(
|
|
2485
|
+
existingSettings = JSON.parse(fs14.readFileSync(settingsPath, "utf8"));
|
|
2441
2486
|
} catch {
|
|
2442
2487
|
stderr(`[cleargate init] WARNING: could not parse ${settingsPath}; treating as empty.
|
|
2443
2488
|
`);
|
|
2444
2489
|
}
|
|
2445
2490
|
}
|
|
2446
2491
|
const mergedSettings = mergeSettings(existingSettings, HOOK_ADDITION);
|
|
2447
|
-
|
|
2492
|
+
fs14.mkdirSync(path14.dirname(settingsPath), { recursive: true });
|
|
2448
2493
|
writeAtomic(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
|
|
2449
2494
|
stdout(`[cleargate init] Updated .claude/settings.json: merged PostToolUse hook
|
|
2450
2495
|
`);
|
|
2451
|
-
const claudeMdPath =
|
|
2452
|
-
const claudeMdSrcPath =
|
|
2496
|
+
const claudeMdPath = path14.join(cwd, "CLAUDE.md");
|
|
2497
|
+
const claudeMdSrcPath = path14.join(payloadDir, "CLAUDE.md");
|
|
2453
2498
|
let claudeMdBlock;
|
|
2454
2499
|
try {
|
|
2455
|
-
const claudeMdSrc =
|
|
2500
|
+
const claudeMdSrc = fs14.readFileSync(claudeMdSrcPath, "utf8");
|
|
2456
2501
|
claudeMdBlock = extractBlock(claudeMdSrc);
|
|
2457
2502
|
} catch (e) {
|
|
2458
2503
|
stderr(`[cleargate init] WARNING: could not read CLAUDE.md block from payload: ${String(e)}
|
|
2459
2504
|
`);
|
|
2460
2505
|
claudeMdBlock = "<!-- CLEARGATE:START -->\n<!-- CLEARGATE:END -->";
|
|
2461
2506
|
}
|
|
2462
|
-
const existingClaudeMd =
|
|
2507
|
+
const existingClaudeMd = fs14.existsSync(claudeMdPath) ? fs14.readFileSync(claudeMdPath, "utf8") : null;
|
|
2463
2508
|
const newClaudeMd = injectClaudeMd(existingClaudeMd, claudeMdBlock);
|
|
2464
2509
|
writeAtomic(claudeMdPath, newClaudeMd);
|
|
2465
2510
|
if (existingClaudeMd === null) {
|
|
@@ -2470,6 +2515,26 @@ async function initHandler(opts = {}) {
|
|
|
2470
2515
|
`);
|
|
2471
2516
|
} else {
|
|
2472
2517
|
stdout(`[cleargate init] CLAUDE.md unchanged (block already up to date)
|
|
2518
|
+
`);
|
|
2519
|
+
}
|
|
2520
|
+
try {
|
|
2521
|
+
const action = injectMcpJson(cwd, "https://cleargate-mcp.soula.ge/mcp");
|
|
2522
|
+
if (action === "created") {
|
|
2523
|
+
stdout(
|
|
2524
|
+
`[cleargate init] Created .mcp.json (cleargate MCP server registered) \u2014 restart Claude Code to load it.
|
|
2525
|
+
`
|
|
2526
|
+
);
|
|
2527
|
+
} else if (action === "updated") {
|
|
2528
|
+
stdout(
|
|
2529
|
+
`[cleargate init] Updated .mcp.json (cleargate MCP server entry merged) \u2014 restart Claude Code to pick up changes.
|
|
2530
|
+
`
|
|
2531
|
+
);
|
|
2532
|
+
} else {
|
|
2533
|
+
stdout(`[cleargate init] .mcp.json unchanged (cleargate entry already present)
|
|
2534
|
+
`);
|
|
2535
|
+
}
|
|
2536
|
+
} catch (e) {
|
|
2537
|
+
stderr(`[cleargate init] WARNING: ${String(e instanceof Error ? e.message : e)}
|
|
2473
2538
|
`);
|
|
2474
2539
|
}
|
|
2475
2540
|
const itemCount = countDeliveryItems(cwd);
|
|
@@ -2483,9 +2548,9 @@ async function initHandler(opts = {}) {
|
|
|
2483
2548
|
stdout(`[cleargate init] Bootstrap: no items to ingest, skipping build
|
|
2484
2549
|
`);
|
|
2485
2550
|
}
|
|
2486
|
-
const cleargateDir =
|
|
2487
|
-
|
|
2488
|
-
const snapshotPath =
|
|
2551
|
+
const cleargateDir = path14.join(cwd, ".cleargate");
|
|
2552
|
+
fs14.mkdirSync(cleargateDir, { recursive: true });
|
|
2553
|
+
const snapshotPath = path14.join(cleargateDir, ".install-manifest.json");
|
|
2489
2554
|
try {
|
|
2490
2555
|
const readManifest = opts.readInstallManifest ?? (() => loadPackageManifest({ packageRoot: payloadDir }));
|
|
2491
2556
|
const pkgManifest = readManifest();
|
|
@@ -2500,19 +2565,19 @@ async function initHandler(opts = {}) {
|
|
|
2500
2565
|
stderr(`[cleargate init] WARNING: could not write install snapshot: ${String(e)}
|
|
2501
2566
|
`);
|
|
2502
2567
|
}
|
|
2503
|
-
if (uninstalledMarker !== null &&
|
|
2568
|
+
if (uninstalledMarker !== null && fs14.existsSync(uninstalledMarkerPath)) {
|
|
2504
2569
|
try {
|
|
2505
|
-
|
|
2570
|
+
fs14.unlinkSync(uninstalledMarkerPath);
|
|
2506
2571
|
} catch (e) {
|
|
2507
2572
|
stderr(`[cleargate init] WARNING: could not remove .uninstalled marker: ${String(e)}
|
|
2508
2573
|
`);
|
|
2509
2574
|
}
|
|
2510
2575
|
}
|
|
2511
2576
|
{
|
|
2512
|
-
const distCliPath =
|
|
2577
|
+
const distCliPath = path14.join(cwd, "cleargate-cli", "dist", "cli.js");
|
|
2513
2578
|
let branch = null;
|
|
2514
2579
|
let branchLabel = "";
|
|
2515
|
-
if (
|
|
2580
|
+
if (fs14.existsSync(distCliPath)) {
|
|
2516
2581
|
branch = { cmd: "node", args: [distCliPath, "--version"] };
|
|
2517
2582
|
branchLabel = `local dist (${distCliPath})`;
|
|
2518
2583
|
} else {
|
|
@@ -2583,8 +2648,8 @@ async function initHandler(opts = {}) {
|
|
|
2583
2648
|
}
|
|
2584
2649
|
|
|
2585
2650
|
// src/commands/wiki-ingest.ts
|
|
2586
|
-
import * as
|
|
2587
|
-
import * as
|
|
2651
|
+
import * as fs15 from "fs";
|
|
2652
|
+
import * as path15 from "path";
|
|
2588
2653
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2589
2654
|
var EXCLUDED_SUFFIXES2 = [
|
|
2590
2655
|
".cleargate/knowledge/",
|
|
@@ -2610,16 +2675,16 @@ async function wikiIngestHandler(opts) {
|
|
|
2610
2675
|
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
2611
2676
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
2612
2677
|
const gitRunner = opts.gitRunner;
|
|
2613
|
-
const rename11 = opts.rename ??
|
|
2678
|
+
const rename11 = opts.rename ?? fs15.renameSync;
|
|
2614
2679
|
const templateDir = opts.templateDir;
|
|
2615
2680
|
const rawPath = opts.rawPath;
|
|
2616
|
-
const absRawPath =
|
|
2617
|
-
const relRawPath =
|
|
2618
|
-
const deliveryRoot =
|
|
2681
|
+
const absRawPath = path15.isAbsolute(rawPath) ? rawPath : path15.resolve(cwd, rawPath);
|
|
2682
|
+
const relRawPath = path15.relative(cwd, absRawPath).replace(/\\/g, "/");
|
|
2683
|
+
const deliveryRoot = path15.join(cwd, ".cleargate", "delivery");
|
|
2619
2684
|
const deliveryRootNorm = deliveryRoot.replace(/\\/g, "/");
|
|
2620
2685
|
const absDeliveryRoot = deliveryRoot;
|
|
2621
|
-
const relToDelivery =
|
|
2622
|
-
if (relToDelivery.startsWith("..") ||
|
|
2686
|
+
const relToDelivery = path15.relative(absDeliveryRoot, absRawPath);
|
|
2687
|
+
if (relToDelivery.startsWith("..") || path15.isAbsolute(relToDelivery)) {
|
|
2623
2688
|
stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
|
|
2624
2689
|
`);
|
|
2625
2690
|
exit(2);
|
|
@@ -2633,7 +2698,7 @@ async function wikiIngestHandler(opts) {
|
|
|
2633
2698
|
exit(0);
|
|
2634
2699
|
return;
|
|
2635
2700
|
}
|
|
2636
|
-
const filename =
|
|
2701
|
+
const filename = path15.basename(absRawPath);
|
|
2637
2702
|
let bucketInfo;
|
|
2638
2703
|
try {
|
|
2639
2704
|
bucketInfo = deriveBucket(filename);
|
|
@@ -2653,12 +2718,12 @@ async function wikiIngestHandler(opts) {
|
|
|
2653
2718
|
return;
|
|
2654
2719
|
}
|
|
2655
2720
|
const { type, id, bucket } = bucketInfo;
|
|
2656
|
-
const wikiRoot =
|
|
2657
|
-
const pageDir =
|
|
2658
|
-
const pagePath =
|
|
2721
|
+
const wikiRoot = path15.join(cwd, ".cleargate", "wiki");
|
|
2722
|
+
const pageDir = path15.join(wikiRoot, bucket);
|
|
2723
|
+
const pagePath = path15.join(pageDir, `${id}.md`);
|
|
2659
2724
|
let rawContent;
|
|
2660
2725
|
try {
|
|
2661
|
-
rawContent =
|
|
2726
|
+
rawContent = fs15.readFileSync(absRawPath, "utf8");
|
|
2662
2727
|
} catch (e) {
|
|
2663
2728
|
stderr(`wiki ingest: cannot read ${rawPath}: ${e.message}
|
|
2664
2729
|
`);
|
|
@@ -2678,11 +2743,11 @@ async function wikiIngestHandler(opts) {
|
|
|
2678
2743
|
return;
|
|
2679
2744
|
}
|
|
2680
2745
|
const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
|
|
2681
|
-
const pageExists =
|
|
2746
|
+
const pageExists = fs15.existsSync(pagePath);
|
|
2682
2747
|
if (pageExists && currentSha !== "") {
|
|
2683
2748
|
let isNoOp = false;
|
|
2684
2749
|
try {
|
|
2685
|
-
const existingPageContent =
|
|
2750
|
+
const existingPageContent = fs15.readFileSync(pagePath, "utf8");
|
|
2686
2751
|
const existingPage = parsePage(existingPageContent);
|
|
2687
2752
|
if (existingPage.last_ingest_commit === currentSha) {
|
|
2688
2753
|
const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
|
|
@@ -2717,8 +2782,8 @@ async function wikiIngestHandler(opts) {
|
|
|
2717
2782
|
};
|
|
2718
2783
|
const pageBody = buildPageBody2({ id, fm, body });
|
|
2719
2784
|
const pageContent = serializePage(wikiPage, pageBody);
|
|
2720
|
-
|
|
2721
|
-
|
|
2785
|
+
fs15.mkdirSync(pageDir, { recursive: true });
|
|
2786
|
+
fs15.writeFileSync(pagePath, pageContent, "utf8");
|
|
2722
2787
|
appendLogEntry(wikiRoot, { timestamp, action, id, relRawPath });
|
|
2723
2788
|
updateIndex(wikiRoot, { id, type, status: wikiPage.status, relRawPath, rename: rename11 });
|
|
2724
2789
|
recompileSynthesis(wikiRoot, cwd, templateDir);
|
|
@@ -2730,7 +2795,7 @@ function checkContentUnchanged(absRawPath, sha, relRawPath, gitRunner) {
|
|
|
2730
2795
|
const run = gitRunner ?? defaultGitRunner;
|
|
2731
2796
|
const gitContent = run("git", ["show", `${sha}:${relRawPath}`]);
|
|
2732
2797
|
if (!gitContent && gitContent !== "") return false;
|
|
2733
|
-
const currentContent =
|
|
2798
|
+
const currentContent = fs15.readFileSync(absRawPath, "utf8");
|
|
2734
2799
|
return gitContent === currentContent;
|
|
2735
2800
|
} catch {
|
|
2736
2801
|
return false;
|
|
@@ -2783,7 +2848,7 @@ function buildPageBody2(item) {
|
|
|
2783
2848
|
].join("\n");
|
|
2784
2849
|
}
|
|
2785
2850
|
function appendLogEntry(wikiRoot, entry) {
|
|
2786
|
-
const logPath =
|
|
2851
|
+
const logPath = path15.join(wikiRoot, "log.md");
|
|
2787
2852
|
const logEntry = [
|
|
2788
2853
|
`- timestamp: "${entry.timestamp}"`,
|
|
2789
2854
|
` actor: "cleargate wiki ingest"`,
|
|
@@ -2791,25 +2856,25 @@ function appendLogEntry(wikiRoot, entry) {
|
|
|
2791
2856
|
` target: "${entry.id}"`,
|
|
2792
2857
|
` path: "${entry.relRawPath}"`
|
|
2793
2858
|
].join("\n");
|
|
2794
|
-
if (
|
|
2795
|
-
const existing =
|
|
2859
|
+
if (fs15.existsSync(logPath)) {
|
|
2860
|
+
const existing = fs15.readFileSync(logPath, "utf8");
|
|
2796
2861
|
const newContent = existing.trimEnd() + "\n" + logEntry + "\n";
|
|
2797
|
-
|
|
2862
|
+
fs15.writeFileSync(logPath, newContent, "utf8");
|
|
2798
2863
|
} else {
|
|
2799
|
-
|
|
2800
|
-
|
|
2864
|
+
fs15.mkdirSync(wikiRoot, { recursive: true });
|
|
2865
|
+
fs15.writeFileSync(logPath, `# Wiki Event Log
|
|
2801
2866
|
|
|
2802
2867
|
${logEntry}
|
|
2803
2868
|
`, "utf8");
|
|
2804
2869
|
}
|
|
2805
2870
|
}
|
|
2806
2871
|
function updateIndex(wikiRoot, opts) {
|
|
2807
|
-
const indexPath =
|
|
2872
|
+
const indexPath = path15.join(wikiRoot, "index.md");
|
|
2808
2873
|
const tmpPath = `${indexPath}.tmp`;
|
|
2809
2874
|
const newRow = `| [[${opts.id}]] | ${opts.type} | ${opts.status} | ${opts.relRawPath} |`;
|
|
2810
2875
|
let content;
|
|
2811
|
-
if (
|
|
2812
|
-
content =
|
|
2876
|
+
if (fs15.existsSync(indexPath)) {
|
|
2877
|
+
content = fs15.readFileSync(indexPath, "utf8");
|
|
2813
2878
|
const idPattern = `[[${opts.id}]]`;
|
|
2814
2879
|
const lines = content.split("\n");
|
|
2815
2880
|
let replaced = false;
|
|
@@ -2828,7 +2893,7 @@ function updateIndex(wikiRoot, opts) {
|
|
|
2828
2893
|
} else {
|
|
2829
2894
|
content = buildMinimalIndex(opts.id, opts.type, opts.status, opts.relRawPath);
|
|
2830
2895
|
}
|
|
2831
|
-
|
|
2896
|
+
fs15.writeFileSync(tmpPath, content, "utf8");
|
|
2832
2897
|
opts.rename(tmpPath, indexPath);
|
|
2833
2898
|
}
|
|
2834
2899
|
function insertIntoSection(content, id, newRow) {
|
|
@@ -2923,39 +2988,39 @@ function buildMinimalIndex(id, type, status, relRawPath) {
|
|
|
2923
2988
|
return lines.join("\n");
|
|
2924
2989
|
}
|
|
2925
2990
|
function recompileSynthesis(wikiRoot, cwd, templateDir) {
|
|
2926
|
-
const deliveryRoot =
|
|
2991
|
+
const deliveryRoot = path15.join(cwd, ".cleargate", "delivery");
|
|
2927
2992
|
let items = [];
|
|
2928
|
-
if (
|
|
2993
|
+
if (fs15.existsSync(deliveryRoot)) {
|
|
2929
2994
|
try {
|
|
2930
2995
|
items = scanRawItems(deliveryRoot, cwd);
|
|
2931
2996
|
} catch {
|
|
2932
2997
|
}
|
|
2933
2998
|
}
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2999
|
+
fs15.writeFileSync(path15.join(wikiRoot, "active-sprint.md"), compile(items, templateDir), "utf8");
|
|
3000
|
+
fs15.writeFileSync(path15.join(wikiRoot, "open-gates.md"), compile2(items, templateDir), "utf8");
|
|
3001
|
+
fs15.writeFileSync(path15.join(wikiRoot, "product-state.md"), compile3(items, templateDir), "utf8");
|
|
3002
|
+
fs15.writeFileSync(path15.join(wikiRoot, "roadmap.md"), compile4(items, templateDir), "utf8");
|
|
2938
3003
|
}
|
|
2939
3004
|
|
|
2940
3005
|
// src/commands/wiki-lint.ts
|
|
2941
|
-
import * as
|
|
3006
|
+
import * as path19 from "path";
|
|
2942
3007
|
|
|
2943
3008
|
// src/wiki/load-wiki.ts
|
|
2944
|
-
import * as
|
|
2945
|
-
import * as
|
|
3009
|
+
import * as fs16 from "fs";
|
|
3010
|
+
import * as path16 from "path";
|
|
2946
3011
|
var BUCKET_DIRS = ["epics", "stories", "sprints", "proposals", "crs", "bugs", "topics"];
|
|
2947
3012
|
function loadWikiPages(wikiRoot) {
|
|
2948
3013
|
const results = [];
|
|
2949
3014
|
for (const bucket of BUCKET_DIRS) {
|
|
2950
|
-
const dir =
|
|
2951
|
-
if (!
|
|
2952
|
-
const entries =
|
|
3015
|
+
const dir = path16.join(wikiRoot, bucket);
|
|
3016
|
+
if (!fs16.existsSync(dir)) continue;
|
|
3017
|
+
const entries = fs16.readdirSync(dir, { encoding: "utf8" });
|
|
2953
3018
|
for (const filename of entries) {
|
|
2954
3019
|
if (!filename.endsWith(".md")) continue;
|
|
2955
|
-
const absPath =
|
|
2956
|
-
const stat =
|
|
3020
|
+
const absPath = path16.join(dir, filename);
|
|
3021
|
+
const stat = fs16.statSync(absPath);
|
|
2957
3022
|
if (!stat.isFile()) continue;
|
|
2958
|
-
const raw =
|
|
3023
|
+
const raw = fs16.readFileSync(absPath, "utf8");
|
|
2959
3024
|
let fm;
|
|
2960
3025
|
let body;
|
|
2961
3026
|
try {
|
|
@@ -2984,8 +3049,8 @@ function loadWikiPages(wikiRoot) {
|
|
|
2984
3049
|
}
|
|
2985
3050
|
|
|
2986
3051
|
// src/wiki/lint-checks.ts
|
|
2987
|
-
import * as
|
|
2988
|
-
import * as
|
|
3052
|
+
import * as fs17 from "fs";
|
|
3053
|
+
import * as path17 from "path";
|
|
2989
3054
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
2990
3055
|
import yaml3 from "js-yaml";
|
|
2991
3056
|
|
|
@@ -3044,9 +3109,9 @@ function checkOrphan(page, repoRoot) {
|
|
|
3044
3109
|
if (!rawPath) return null;
|
|
3045
3110
|
const isExcluded = EXCLUDED_DIRS.some((excl) => rawPath.startsWith(excl));
|
|
3046
3111
|
if (isExcluded) return null;
|
|
3047
|
-
const absRaw =
|
|
3048
|
-
if (!
|
|
3049
|
-
const relPage =
|
|
3112
|
+
const absRaw = path17.join(repoRoot, rawPath);
|
|
3113
|
+
if (!fs17.existsSync(absRaw)) {
|
|
3114
|
+
const relPage = path17.relative(path17.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3050
3115
|
return {
|
|
3051
3116
|
category: "orphan",
|
|
3052
3117
|
line: `orphan: ${relPage} -> missing ${rawPath} (raw missing)`
|
|
@@ -3064,7 +3129,7 @@ function checkRepoMismatch(page, repoRoot) {
|
|
|
3064
3129
|
return null;
|
|
3065
3130
|
}
|
|
3066
3131
|
if (page.page.repo !== derivedRepo) {
|
|
3067
|
-
const relPage =
|
|
3132
|
+
const relPage = path17.relative(path17.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3068
3133
|
return {
|
|
3069
3134
|
category: "repo-mismatch",
|
|
3070
3135
|
line: `repo-mismatch: ${relPage} declares repo:${page.page.repo} but raw_path implies repo:${derivedRepo}`
|
|
@@ -3089,7 +3154,7 @@ function checkStaleCommit(page, repoRoot, gitRunner) {
|
|
|
3089
3154
|
}
|
|
3090
3155
|
if (!currentSha) return null;
|
|
3091
3156
|
if (storedSha !== currentSha) {
|
|
3092
|
-
const relPage =
|
|
3157
|
+
const relPage = path17.relative(path17.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3093
3158
|
return {
|
|
3094
3159
|
category: "stale-commit",
|
|
3095
3160
|
line: `stale-commit: ${relPage} at ${storedSha}, current ${currentSha}`
|
|
@@ -3100,14 +3165,14 @@ function checkStaleCommit(page, repoRoot, gitRunner) {
|
|
|
3100
3165
|
function checkMissingIngest(page, repoRoot) {
|
|
3101
3166
|
const rawPath = page.page.raw_path;
|
|
3102
3167
|
if (!rawPath) return null;
|
|
3103
|
-
const absRaw =
|
|
3104
|
-
if (!
|
|
3105
|
-
const rawStat =
|
|
3106
|
-
const pageStat =
|
|
3168
|
+
const absRaw = path17.join(repoRoot, rawPath);
|
|
3169
|
+
if (!fs17.existsSync(absRaw)) return null;
|
|
3170
|
+
const rawStat = fs17.statSync(absRaw);
|
|
3171
|
+
const pageStat = fs17.statSync(page.absPath);
|
|
3107
3172
|
const rawMtimeMs = rawStat.mtimeMs;
|
|
3108
3173
|
const pageMtimeMs = pageStat.mtimeMs;
|
|
3109
3174
|
if (rawMtimeMs - pageMtimeMs > 2e3) {
|
|
3110
|
-
const relPage =
|
|
3175
|
+
const relPage = path17.relative(path17.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3111
3176
|
const rawMtime = rawStat.mtime.toISOString();
|
|
3112
3177
|
const pageMtime = pageStat.mtime.toISOString();
|
|
3113
3178
|
return {
|
|
@@ -3118,7 +3183,7 @@ function checkMissingIngest(page, repoRoot) {
|
|
|
3118
3183
|
return null;
|
|
3119
3184
|
}
|
|
3120
3185
|
function checkBrokenBacklinks(pages, repoRoot) {
|
|
3121
|
-
const wikiRoot =
|
|
3186
|
+
const wikiRoot = path17.join(repoRoot, ".cleargate", "wiki");
|
|
3122
3187
|
const byId = /* @__PURE__ */ new Map();
|
|
3123
3188
|
for (const p of pages) {
|
|
3124
3189
|
if (p.page.id) byId.set(p.page.id, p);
|
|
@@ -3132,7 +3197,7 @@ function checkBrokenBacklinks(pages, repoRoot) {
|
|
|
3132
3197
|
const parentId = match[1];
|
|
3133
3198
|
const parentPage = byId.get(parentId);
|
|
3134
3199
|
if (!parentPage) {
|
|
3135
|
-
const relChild =
|
|
3200
|
+
const relChild = path17.relative(wikiRoot, childPage.absPath).replace(/\\/g, "/");
|
|
3136
3201
|
findings.push({
|
|
3137
3202
|
category: "broken-backlink",
|
|
3138
3203
|
line: `broken-backlink: ${relChild} -> ${parentId} (parent missing child entry)`
|
|
@@ -3145,7 +3210,7 @@ function checkBrokenBacklinks(pages, repoRoot) {
|
|
|
3145
3210
|
(c) => c === childRef || c === childId
|
|
3146
3211
|
);
|
|
3147
3212
|
if (!parentHasChild) {
|
|
3148
|
-
const relChild =
|
|
3213
|
+
const relChild = path17.relative(wikiRoot, childPage.absPath).replace(/\\/g, "/");
|
|
3149
3214
|
findings.push({
|
|
3150
3215
|
category: "broken-backlink",
|
|
3151
3216
|
line: `broken-backlink: ${relChild} -> ${parentId} (parent missing child entry)`
|
|
@@ -3155,7 +3220,7 @@ function checkBrokenBacklinks(pages, repoRoot) {
|
|
|
3155
3220
|
return findings;
|
|
3156
3221
|
}
|
|
3157
3222
|
function checkInvalidatedCitations(pages, repoRoot) {
|
|
3158
|
-
const wikiRoot =
|
|
3223
|
+
const wikiRoot = path17.join(repoRoot, ".cleargate", "wiki");
|
|
3159
3224
|
const byId = /* @__PURE__ */ new Map();
|
|
3160
3225
|
for (const p of pages) {
|
|
3161
3226
|
if (p.page.id) byId.set(p.page.id, p);
|
|
@@ -3163,10 +3228,10 @@ function checkInvalidatedCitations(pages, repoRoot) {
|
|
|
3163
3228
|
const findings = [];
|
|
3164
3229
|
const topicPages = pages.filter((p) => p.page.type === "topic");
|
|
3165
3230
|
for (const topicPage of topicPages) {
|
|
3166
|
-
const relTopic =
|
|
3231
|
+
const relTopic = path17.relative(wikiRoot, topicPage.absPath).replace(/\\/g, "/");
|
|
3167
3232
|
let citesList = [];
|
|
3168
3233
|
try {
|
|
3169
|
-
const raw =
|
|
3234
|
+
const raw = fs17.readFileSync(topicPage.absPath, "utf8");
|
|
3170
3235
|
const { fm } = parseFrontmatter(raw);
|
|
3171
3236
|
const rawCites = fm["cites"];
|
|
3172
3237
|
if (Array.isArray(rawCites)) {
|
|
@@ -3202,7 +3267,7 @@ function checkExcludedPathIngested(page, repoRoot) {
|
|
|
3202
3267
|
if (!rawPath) return null;
|
|
3203
3268
|
const isExcluded = EXCLUDED_DIRS.some((excl) => rawPath.startsWith(excl));
|
|
3204
3269
|
if (isExcluded) {
|
|
3205
|
-
const relPage =
|
|
3270
|
+
const relPage = path17.relative(path17.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3206
3271
|
return {
|
|
3207
3272
|
category: "excluded-path-ingested",
|
|
3208
3273
|
line: `excluded-path-ingested: ${relPage} (raw_path ${rawPath} is under an excluded directory)`
|
|
@@ -3213,7 +3278,7 @@ function checkExcludedPathIngested(page, repoRoot) {
|
|
|
3213
3278
|
function checkPaginationNeeded(pages) {
|
|
3214
3279
|
const bucketCounts = /* @__PURE__ */ new Map();
|
|
3215
3280
|
for (const p of pages) {
|
|
3216
|
-
const bucket =
|
|
3281
|
+
const bucket = path17.basename(path17.dirname(p.absPath));
|
|
3217
3282
|
bucketCounts.set(bucket, (bucketCounts.get(bucket) ?? 0) + 1);
|
|
3218
3283
|
}
|
|
3219
3284
|
const findings = [];
|
|
@@ -3251,11 +3316,11 @@ function parseCachedGateResult(raw) {
|
|
|
3251
3316
|
function checkGateFailure(page, repoRoot) {
|
|
3252
3317
|
const rawPath = page.page.raw_path;
|
|
3253
3318
|
if (!rawPath) return null;
|
|
3254
|
-
const absRaw =
|
|
3255
|
-
if (!
|
|
3319
|
+
const absRaw = path17.join(repoRoot, rawPath);
|
|
3320
|
+
if (!fs17.existsSync(absRaw)) return null;
|
|
3256
3321
|
let rawFm;
|
|
3257
3322
|
try {
|
|
3258
|
-
const raw =
|
|
3323
|
+
const raw = fs17.readFileSync(absRaw, "utf8");
|
|
3259
3324
|
const { fm } = parseFrontmatter(raw);
|
|
3260
3325
|
rawFm = fm;
|
|
3261
3326
|
} catch {
|
|
@@ -3289,11 +3354,11 @@ function checkGateFailure(page, repoRoot) {
|
|
|
3289
3354
|
function checkGateStaleness(page, repoRoot) {
|
|
3290
3355
|
const rawPath = page.page.raw_path;
|
|
3291
3356
|
if (!rawPath) return null;
|
|
3292
|
-
const absRaw =
|
|
3293
|
-
if (!
|
|
3357
|
+
const absRaw = path17.join(repoRoot, rawPath);
|
|
3358
|
+
if (!fs17.existsSync(absRaw)) return null;
|
|
3294
3359
|
let rawFm;
|
|
3295
3360
|
try {
|
|
3296
|
-
const raw =
|
|
3361
|
+
const raw = fs17.readFileSync(absRaw, "utf8");
|
|
3297
3362
|
const { fm } = parseFrontmatter(raw);
|
|
3298
3363
|
rawFm = fm;
|
|
3299
3364
|
} catch {
|
|
@@ -3316,7 +3381,7 @@ function checkGateStaleness(page, repoRoot) {
|
|
|
3316
3381
|
return null;
|
|
3317
3382
|
}
|
|
3318
3383
|
function discoverPlainTextMentions(pages, repoRoot) {
|
|
3319
|
-
const wikiRoot =
|
|
3384
|
+
const wikiRoot = path17.join(repoRoot, ".cleargate", "wiki");
|
|
3320
3385
|
const byId = /* @__PURE__ */ new Map();
|
|
3321
3386
|
for (const p of pages) {
|
|
3322
3387
|
if (p.page.id) byId.set(p.page.id, true);
|
|
@@ -3325,7 +3390,7 @@ function discoverPlainTextMentions(pages, repoRoot) {
|
|
|
3325
3390
|
const ID_PATTERN = /\b((?:EPIC|STORY|SPRINT|PROPOSAL|CR|BUG)-[\w-]+)\b/g;
|
|
3326
3391
|
const LINK_PATTERN = /\[\[[\w-]+\]\]/g;
|
|
3327
3392
|
for (const page of pages) {
|
|
3328
|
-
const relPage =
|
|
3393
|
+
const relPage = path17.relative(wikiRoot, page.absPath).replace(/\\/g, "/");
|
|
3329
3394
|
const wrappedRefs = /* @__PURE__ */ new Set();
|
|
3330
3395
|
for (const m of page.body.matchAll(LINK_PATTERN)) {
|
|
3331
3396
|
const inner = m[0].slice(2, -2);
|
|
@@ -3342,11 +3407,11 @@ function discoverPlainTextMentions(pages, repoRoot) {
|
|
|
3342
3407
|
return suggestions;
|
|
3343
3408
|
}
|
|
3344
3409
|
function checkIndexBudget(repoRoot, indexTokenCeiling) {
|
|
3345
|
-
const indexPath =
|
|
3346
|
-
if (!
|
|
3410
|
+
const indexPath = path17.join(repoRoot, ".cleargate", "wiki", "index.md");
|
|
3411
|
+
if (!fs17.existsSync(indexPath)) {
|
|
3347
3412
|
return { finding: null };
|
|
3348
3413
|
}
|
|
3349
|
-
const bytes =
|
|
3414
|
+
const bytes = fs17.statSync(indexPath).size;
|
|
3350
3415
|
const tokens = Math.round(bytes / 4);
|
|
3351
3416
|
const ceiling = indexTokenCeiling;
|
|
3352
3417
|
if (tokens > ceiling) {
|
|
@@ -3363,18 +3428,18 @@ function checkIndexBudget(repoRoot, indexTokenCeiling) {
|
|
|
3363
3428
|
}
|
|
3364
3429
|
|
|
3365
3430
|
// src/lib/wiki-config.ts
|
|
3366
|
-
import * as
|
|
3367
|
-
import * as
|
|
3431
|
+
import * as fs18 from "fs";
|
|
3432
|
+
import * as path18 from "path";
|
|
3368
3433
|
import yaml4 from "js-yaml";
|
|
3369
3434
|
var DEFAULT_INDEX_TOKEN_CEILING = 8e3;
|
|
3370
3435
|
function loadWikiConfig(repoRoot) {
|
|
3371
|
-
const configPath =
|
|
3372
|
-
if (!
|
|
3436
|
+
const configPath = path18.join(repoRoot, ".cleargate", "config.yml");
|
|
3437
|
+
if (!fs18.existsSync(configPath)) {
|
|
3373
3438
|
return { wiki: { index_token_ceiling: DEFAULT_INDEX_TOKEN_CEILING }, gates: {} };
|
|
3374
3439
|
}
|
|
3375
3440
|
let raw;
|
|
3376
3441
|
try {
|
|
3377
|
-
raw =
|
|
3442
|
+
raw = fs18.readFileSync(configPath, "utf8");
|
|
3378
3443
|
} catch (err) {
|
|
3379
3444
|
throw new Error(`Failed to read ${configPath}: ${String(err)}`);
|
|
3380
3445
|
}
|
|
@@ -3435,7 +3500,7 @@ async function wikiLintHandler(opts = {}) {
|
|
|
3435
3500
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
3436
3501
|
const gitRunner = opts.gitRunner;
|
|
3437
3502
|
const mode = opts.mode ?? "enforce";
|
|
3438
|
-
const wikiRoot =
|
|
3503
|
+
const wikiRoot = path19.join(cwd, ".cleargate", "wiki");
|
|
3439
3504
|
const repoRoot = cwd;
|
|
3440
3505
|
let pages = loadWikiPages(wikiRoot);
|
|
3441
3506
|
const findings = [];
|
|
@@ -3506,8 +3571,8 @@ async function wikiLintHandler(opts = {}) {
|
|
|
3506
3571
|
}
|
|
3507
3572
|
|
|
3508
3573
|
// src/commands/wiki-query.ts
|
|
3509
|
-
import * as
|
|
3510
|
-
import * as
|
|
3574
|
+
import * as fs19 from "fs";
|
|
3575
|
+
import * as path20 from "path";
|
|
3511
3576
|
function computeSlug(query) {
|
|
3512
3577
|
return query.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 40).replace(/-+$/, "");
|
|
3513
3578
|
}
|
|
@@ -3537,9 +3602,9 @@ async function wikiQueryHandler(opts) {
|
|
|
3537
3602
|
const query = opts.query;
|
|
3538
3603
|
const persist = opts.persist ?? false;
|
|
3539
3604
|
void stderr;
|
|
3540
|
-
const wikiRoot =
|
|
3541
|
-
const indexPath =
|
|
3542
|
-
if (!
|
|
3605
|
+
const wikiRoot = path20.join(cwd, ".cleargate", "wiki");
|
|
3606
|
+
const indexPath = path20.join(wikiRoot, "index.md");
|
|
3607
|
+
if (!fs19.existsSync(indexPath)) {
|
|
3543
3608
|
stdout(`wiki query: no index.md found at ${indexPath}
|
|
3544
3609
|
`);
|
|
3545
3610
|
stdout(`Run \`cleargate wiki build\` first.
|
|
@@ -3547,7 +3612,7 @@ async function wikiQueryHandler(opts) {
|
|
|
3547
3612
|
exit(1);
|
|
3548
3613
|
return;
|
|
3549
3614
|
}
|
|
3550
|
-
const indexContent =
|
|
3615
|
+
const indexContent = fs19.readFileSync(indexPath, "utf8");
|
|
3551
3616
|
const matches = searchIndex(indexContent, query);
|
|
3552
3617
|
if (matches.length === 0) {
|
|
3553
3618
|
stdout(`wiki query: no matches for "${query}"
|
|
@@ -3572,8 +3637,8 @@ async function wikiQueryHandler(opts) {
|
|
|
3572
3637
|
return;
|
|
3573
3638
|
}
|
|
3574
3639
|
const slug = computeSlug(query);
|
|
3575
|
-
const topicsDir =
|
|
3576
|
-
|
|
3640
|
+
const topicsDir = path20.join(wikiRoot, "topics");
|
|
3641
|
+
fs19.mkdirSync(topicsDir, { recursive: true });
|
|
3577
3642
|
const citesArray = matches.map(({ id }) => `"[[${id}]]"`);
|
|
3578
3643
|
const createdAt = now();
|
|
3579
3644
|
const frontmatter = [
|
|
@@ -3588,13 +3653,13 @@ async function wikiQueryHandler(opts) {
|
|
|
3588
3653
|
const topicContent = `${frontmatter}
|
|
3589
3654
|
|
|
3590
3655
|
${body}`;
|
|
3591
|
-
const topicPath =
|
|
3592
|
-
|
|
3656
|
+
const topicPath = path20.join(topicsDir, `${slug}.md`);
|
|
3657
|
+
fs19.writeFileSync(topicPath, topicContent, "utf8");
|
|
3593
3658
|
updateIndexTopicsSection(indexPath, slug, query, createdAt);
|
|
3594
3659
|
exit(0);
|
|
3595
3660
|
}
|
|
3596
3661
|
function updateIndexTopicsSection(indexPath, slug, query, createdAt) {
|
|
3597
|
-
let content =
|
|
3662
|
+
let content = fs19.readFileSync(indexPath, "utf8");
|
|
3598
3663
|
const row = `| ${slug} | ${query} | ${createdAt} |`;
|
|
3599
3664
|
if (content.includes("## Topics")) {
|
|
3600
3665
|
const topicsIdx = content.indexOf("## Topics");
|
|
@@ -3617,12 +3682,12 @@ ${row}
|
|
|
3617
3682
|
${row}
|
|
3618
3683
|
`;
|
|
3619
3684
|
}
|
|
3620
|
-
|
|
3685
|
+
fs19.writeFileSync(indexPath, content, "utf8");
|
|
3621
3686
|
}
|
|
3622
3687
|
|
|
3623
3688
|
// src/commands/wiki-audit-status.ts
|
|
3624
|
-
import * as
|
|
3625
|
-
import * as
|
|
3689
|
+
import * as fs20 from "fs";
|
|
3690
|
+
import * as path21 from "path";
|
|
3626
3691
|
import * as readline4 from "readline";
|
|
3627
3692
|
var TERMINAL = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
|
|
3628
3693
|
async function wikiAuditStatusHandler(opts = {}) {
|
|
@@ -3635,8 +3700,8 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
3635
3700
|
});
|
|
3636
3701
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
3637
3702
|
const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
|
|
3638
|
-
const deliveryRoot =
|
|
3639
|
-
if (!
|
|
3703
|
+
const deliveryRoot = path21.join(cwd, ".cleargate", "delivery");
|
|
3704
|
+
if (!fs20.existsSync(deliveryRoot)) {
|
|
3640
3705
|
stderr(`audit-status: .cleargate/delivery/ not found at ${deliveryRoot}
|
|
3641
3706
|
`);
|
|
3642
3707
|
exit(1);
|
|
@@ -3776,7 +3841,7 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
3776
3841
|
}
|
|
3777
3842
|
}
|
|
3778
3843
|
for (const d of fixable) {
|
|
3779
|
-
const rawText =
|
|
3844
|
+
const rawText = fs20.readFileSync(d.absPath, "utf8");
|
|
3780
3845
|
const updated = applyStatusFix(rawText, d.suggestedStatus);
|
|
3781
3846
|
if (!opts.quiet) {
|
|
3782
3847
|
stdout(`--- ${d.rawPath}
|
|
@@ -3792,7 +3857,7 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
3792
3857
|
stdout(`+${newLine}
|
|
3793
3858
|
`);
|
|
3794
3859
|
}
|
|
3795
|
-
|
|
3860
|
+
fs20.writeFileSync(d.absPath, updated, "utf8");
|
|
3796
3861
|
}
|
|
3797
3862
|
stdout(`audit-status: applied ${fixable.length} fix(es)
|
|
3798
3863
|
`);
|
|
@@ -3820,8 +3885,8 @@ function applyStatusFix(rawText, newStatus) {
|
|
|
3820
3885
|
}
|
|
3821
3886
|
|
|
3822
3887
|
// src/commands/doctor.ts
|
|
3823
|
-
import * as
|
|
3824
|
-
import * as
|
|
3888
|
+
import * as fs21 from "fs";
|
|
3889
|
+
import * as path22 from "path";
|
|
3825
3890
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
3826
3891
|
|
|
3827
3892
|
// src/lib/pricing.ts
|
|
@@ -3897,24 +3962,24 @@ function parseHookLogLine(line) {
|
|
|
3897
3962
|
};
|
|
3898
3963
|
}
|
|
3899
3964
|
function runHookHealth(stdout, cwd, now, outcome) {
|
|
3900
|
-
const cleargateDir =
|
|
3901
|
-
if (!
|
|
3965
|
+
const cleargateDir = path22.join(cwd, ".cleargate");
|
|
3966
|
+
if (!fs21.existsSync(cleargateDir)) {
|
|
3902
3967
|
stdout("cleargate misconfigured: no .cleargate/ found. Run: cleargate init");
|
|
3903
3968
|
if (outcome) outcome.configError = true;
|
|
3904
3969
|
return;
|
|
3905
3970
|
}
|
|
3906
|
-
const manifestPath =
|
|
3907
|
-
if (!
|
|
3971
|
+
const manifestPath = path22.join(cwd, "cleargate-planning", "MANIFEST.json");
|
|
3972
|
+
if (!fs21.existsSync(manifestPath)) {
|
|
3908
3973
|
stdout(`cleargate misconfigured: cleargate-planning/MANIFEST.json not found. Run: cleargate init`);
|
|
3909
3974
|
if (outcome) outcome.configError = true;
|
|
3910
3975
|
}
|
|
3911
|
-
const settingsPath =
|
|
3912
|
-
if (!
|
|
3976
|
+
const settingsPath = path22.join(cwd, ".claude", "settings.json");
|
|
3977
|
+
if (!fs21.existsSync(settingsPath)) {
|
|
3913
3978
|
stdout("[doctor] No .claude/settings.json found \u2014 hook config unavailable.");
|
|
3914
3979
|
return;
|
|
3915
3980
|
}
|
|
3916
3981
|
try {
|
|
3917
|
-
const raw =
|
|
3982
|
+
const raw = fs21.readFileSync(settingsPath, "utf-8");
|
|
3918
3983
|
const settings = JSON.parse(raw);
|
|
3919
3984
|
const hasHooks = typeof settings === "object" && settings !== null && "hooks" in settings;
|
|
3920
3985
|
if (hasHooks) {
|
|
@@ -3925,13 +3990,13 @@ function runHookHealth(stdout, cwd, now, outcome) {
|
|
|
3925
3990
|
} catch {
|
|
3926
3991
|
stdout("[doctor] .claude/settings.json is not valid JSON \u2014 cannot verify hook config.");
|
|
3927
3992
|
}
|
|
3928
|
-
const logPath =
|
|
3929
|
-
if (!
|
|
3993
|
+
const logPath = path22.join(cwd, ".cleargate", "hook-log", "gate-check.log");
|
|
3994
|
+
if (!fs21.existsSync(logPath)) {
|
|
3930
3995
|
return;
|
|
3931
3996
|
}
|
|
3932
3997
|
let logContent;
|
|
3933
3998
|
try {
|
|
3934
|
-
logContent =
|
|
3999
|
+
logContent = fs21.readFileSync(logPath, "utf-8");
|
|
3935
4000
|
} catch {
|
|
3936
4001
|
return;
|
|
3937
4002
|
}
|
|
@@ -4045,8 +4110,8 @@ function parseCachedGateResult2(raw) {
|
|
|
4045
4110
|
};
|
|
4046
4111
|
}
|
|
4047
4112
|
function emitResolverStatusLine(cwd, stdout) {
|
|
4048
|
-
const distCliPath =
|
|
4049
|
-
if (
|
|
4113
|
+
const distCliPath = path22.join(cwd, "cleargate-cli", "dist", "cli.js");
|
|
4114
|
+
if (fs21.existsSync(distCliPath)) {
|
|
4050
4115
|
stdout(`cleargate CLI: local dist \u2014 ${distCliPath}`);
|
|
4051
4116
|
return;
|
|
4052
4117
|
}
|
|
@@ -4060,10 +4125,10 @@ function emitResolverStatusLine(cwd, stdout) {
|
|
|
4060
4125
|
return;
|
|
4061
4126
|
}
|
|
4062
4127
|
let pinVersion = "unknown";
|
|
4063
|
-
const hookPath =
|
|
4064
|
-
if (
|
|
4128
|
+
const hookPath = path22.join(cwd, ".claude", "hooks", "stamp-and-gate.sh");
|
|
4129
|
+
if (fs21.existsSync(hookPath)) {
|
|
4065
4130
|
try {
|
|
4066
|
-
const hookContent =
|
|
4131
|
+
const hookContent = fs21.readFileSync(hookPath, "utf-8");
|
|
4067
4132
|
const pinMatch = hookContent.match(/^#\s*cleargate-pin:\s*(\S+)\s*$/m);
|
|
4068
4133
|
if (pinMatch?.[1]) {
|
|
4069
4134
|
pinVersion = pinMatch[1];
|
|
@@ -4095,10 +4160,10 @@ async function runSessionStart(cwd, stdout, outcome) {
|
|
|
4095
4160
|
if (outcome && resolverLines.some((l) => l.includes("\u{1F534}"))) {
|
|
4096
4161
|
outcome.configError = true;
|
|
4097
4162
|
}
|
|
4098
|
-
const pendingSyncDir =
|
|
4163
|
+
const pendingSyncDir = path22.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
4099
4164
|
let files;
|
|
4100
4165
|
try {
|
|
4101
|
-
files =
|
|
4166
|
+
files = fs21.readdirSync(pendingSyncDir).filter((f) => f.endsWith(".md")).map((f) => path22.join(pendingSyncDir, f));
|
|
4102
4167
|
} catch {
|
|
4103
4168
|
return;
|
|
4104
4169
|
}
|
|
@@ -4107,7 +4172,7 @@ async function runSessionStart(cwd, stdout, outcome) {
|
|
|
4107
4172
|
for (const filePath of files) {
|
|
4108
4173
|
let raw;
|
|
4109
4174
|
try {
|
|
4110
|
-
raw =
|
|
4175
|
+
raw = fs21.readFileSync(filePath, "utf-8");
|
|
4111
4176
|
} catch {
|
|
4112
4177
|
continue;
|
|
4113
4178
|
}
|
|
@@ -4133,13 +4198,13 @@ async function runSessionStart(cwd, stdout, outcome) {
|
|
|
4133
4198
|
}
|
|
4134
4199
|
}
|
|
4135
4200
|
if (!itemId) {
|
|
4136
|
-
itemId =
|
|
4201
|
+
itemId = path22.basename(filePath, ".md");
|
|
4137
4202
|
}
|
|
4138
4203
|
const firstCriterionId = gate2.failing_criteria.length > 0 ? gate2.failing_criteria[0]?.id ?? "" : "";
|
|
4139
4204
|
blocked.push({ id: itemId, firstCriterionId });
|
|
4140
4205
|
}
|
|
4141
|
-
const activesentinel =
|
|
4142
|
-
const sprintActive =
|
|
4206
|
+
const activesentinel = path22.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
4207
|
+
const sprintActive = fs21.existsSync(activesentinel);
|
|
4143
4208
|
const shouldRemind = !hasApprovedStory && !sprintActive;
|
|
4144
4209
|
if (shouldRemind) {
|
|
4145
4210
|
stdout(PLANNING_FIRST_REMINDER);
|
|
@@ -4174,10 +4239,10 @@ async function runPricing(filePath, cwd, stdout, stderr, exit, outcome) {
|
|
|
4174
4239
|
exit(2);
|
|
4175
4240
|
return;
|
|
4176
4241
|
}
|
|
4177
|
-
const absPath =
|
|
4242
|
+
const absPath = path22.isAbsolute(filePath) ? filePath : path22.resolve(cwd, filePath);
|
|
4178
4243
|
let raw;
|
|
4179
4244
|
try {
|
|
4180
|
-
raw =
|
|
4245
|
+
raw = fs21.readFileSync(absPath, "utf-8");
|
|
4181
4246
|
} catch {
|
|
4182
4247
|
stderr(`cleargate doctor --pricing: cannot read file: ${absPath}`);
|
|
4183
4248
|
if (outcome) outcome.configError = true;
|
|
@@ -4239,7 +4304,7 @@ async function runPricing(filePath, cwd, stdout, stderr, exit, outcome) {
|
|
|
4239
4304
|
const output = draftTokens.output ?? 0;
|
|
4240
4305
|
const cacheRead = draftTokens.cache_read ?? 0;
|
|
4241
4306
|
const cacheCreation = draftTokens.cache_creation ?? 0;
|
|
4242
|
-
const fileName =
|
|
4307
|
+
const fileName = path22.basename(absPath);
|
|
4243
4308
|
stdout(
|
|
4244
4309
|
`${fileName}: ${model} \u2014 input:${input} output:${output} cache_read:${cacheRead} cache_creation:${cacheCreation} \u2248 $${usd.toFixed(4)}`
|
|
4245
4310
|
);
|
|
@@ -4252,15 +4317,15 @@ function globMatch(pattern, filePath) {
|
|
|
4252
4317
|
return re.test(normalFile);
|
|
4253
4318
|
}
|
|
4254
4319
|
async function runCanEdit(filePath, cwd, stdout, exit, outcome) {
|
|
4255
|
-
const activeSentinel =
|
|
4256
|
-
if (
|
|
4320
|
+
const activeSentinel = path22.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
4321
|
+
if (fs21.existsSync(activeSentinel)) {
|
|
4257
4322
|
stdout("allowed: sprint active");
|
|
4258
4323
|
return;
|
|
4259
4324
|
}
|
|
4260
|
-
const pendingSyncDir =
|
|
4325
|
+
const pendingSyncDir = path22.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
4261
4326
|
let files;
|
|
4262
4327
|
try {
|
|
4263
|
-
files =
|
|
4328
|
+
files = fs21.readdirSync(pendingSyncDir).filter((f) => f.endsWith(".md")).map((f) => path22.join(pendingSyncDir, f));
|
|
4264
4329
|
} catch {
|
|
4265
4330
|
stdout("blocked: no_approved_stories");
|
|
4266
4331
|
if (outcome) outcome.blocker = true;
|
|
@@ -4272,7 +4337,7 @@ async function runCanEdit(filePath, cwd, stdout, exit, outcome) {
|
|
|
4272
4337
|
for (const storyPath of files) {
|
|
4273
4338
|
let raw;
|
|
4274
4339
|
try {
|
|
4275
|
-
raw =
|
|
4340
|
+
raw = fs21.readFileSync(storyPath, "utf-8");
|
|
4276
4341
|
} catch {
|
|
4277
4342
|
continue;
|
|
4278
4343
|
}
|
|
@@ -4369,13 +4434,13 @@ async function doctorHandler(flags, cli) {
|
|
|
4369
4434
|
}
|
|
4370
4435
|
|
|
4371
4436
|
// src/commands/gate.ts
|
|
4372
|
-
import * as
|
|
4373
|
-
import * as
|
|
4437
|
+
import * as fs25 from "fs";
|
|
4438
|
+
import * as path25 from "path";
|
|
4374
4439
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
4375
4440
|
|
|
4376
4441
|
// src/commands/execution-mode.ts
|
|
4377
|
-
import * as
|
|
4378
|
-
import * as
|
|
4442
|
+
import * as fs22 from "fs";
|
|
4443
|
+
import * as path23 from "path";
|
|
4379
4444
|
var V1_INERT_MESSAGE = "v1 mode active \u2014 command inert. Set execution_mode: v2 in sprint frontmatter to enable.";
|
|
4380
4445
|
function parseFrontmatterSimple(raw) {
|
|
4381
4446
|
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
@@ -4393,24 +4458,24 @@ function parseFrontmatterSimple(raw) {
|
|
|
4393
4458
|
}
|
|
4394
4459
|
function discoverSprintFile(sprintId, cwd) {
|
|
4395
4460
|
const searchDirs = [
|
|
4396
|
-
|
|
4397
|
-
|
|
4461
|
+
path23.join(cwd, ".cleargate", "delivery", "pending-sync"),
|
|
4462
|
+
path23.join(cwd, ".cleargate", "delivery", "archive")
|
|
4398
4463
|
];
|
|
4399
4464
|
for (const dir of searchDirs) {
|
|
4400
|
-
if (!
|
|
4465
|
+
if (!fs22.existsSync(dir)) continue;
|
|
4401
4466
|
let entries;
|
|
4402
4467
|
try {
|
|
4403
|
-
entries =
|
|
4468
|
+
entries = fs22.readdirSync(dir);
|
|
4404
4469
|
} catch {
|
|
4405
4470
|
continue;
|
|
4406
4471
|
}
|
|
4407
4472
|
const prefix = `${sprintId}_`;
|
|
4408
4473
|
for (const entry of entries) {
|
|
4409
4474
|
if (entry.startsWith(prefix) && entry.endsWith(".md")) {
|
|
4410
|
-
return
|
|
4475
|
+
return path23.join(dir, entry);
|
|
4411
4476
|
}
|
|
4412
4477
|
if (entry === `${sprintId}.md`) {
|
|
4413
|
-
return
|
|
4478
|
+
return path23.join(dir, entry);
|
|
4414
4479
|
}
|
|
4415
4480
|
}
|
|
4416
4481
|
}
|
|
@@ -4418,9 +4483,9 @@ function discoverSprintFile(sprintId, cwd) {
|
|
|
4418
4483
|
}
|
|
4419
4484
|
function resolveSprintIdFromSentinel(cwd) {
|
|
4420
4485
|
const resolvedCwd = cwd ?? process.cwd();
|
|
4421
|
-
const sentinelPath =
|
|
4486
|
+
const sentinelPath = path23.join(resolvedCwd, ".cleargate", "sprint-runs", ".active");
|
|
4422
4487
|
try {
|
|
4423
|
-
const content =
|
|
4488
|
+
const content = fs22.readFileSync(sentinelPath, "utf8").trim();
|
|
4424
4489
|
return content.length > 0 ? content : null;
|
|
4425
4490
|
} catch {
|
|
4426
4491
|
return null;
|
|
@@ -4439,12 +4504,12 @@ function readSprintExecutionMode(sprintId, opts = {}) {
|
|
|
4439
4504
|
if (!filePath) {
|
|
4440
4505
|
filePath = discoverSprintFile(resolvedSprintId, cwd);
|
|
4441
4506
|
}
|
|
4442
|
-
if (!filePath || !
|
|
4507
|
+
if (!filePath || !fs22.existsSync(filePath)) {
|
|
4443
4508
|
return "v1";
|
|
4444
4509
|
}
|
|
4445
4510
|
let raw;
|
|
4446
4511
|
try {
|
|
4447
|
-
raw =
|
|
4512
|
+
raw = fs22.readFileSync(filePath, "utf8");
|
|
4448
4513
|
} catch {
|
|
4449
4514
|
return "v1";
|
|
4450
4515
|
}
|
|
@@ -4462,8 +4527,8 @@ function printInertAndExit(stdoutFn, exitFn) {
|
|
|
4462
4527
|
import yaml6 from "js-yaml";
|
|
4463
4528
|
|
|
4464
4529
|
// src/lib/readiness-predicates.ts
|
|
4465
|
-
import * as
|
|
4466
|
-
import * as
|
|
4530
|
+
import * as fs23 from "fs";
|
|
4531
|
+
import * as path24 from "path";
|
|
4467
4532
|
function parsePredicate(src) {
|
|
4468
4533
|
const s = src.trim();
|
|
4469
4534
|
const fmMatch = s.match(
|
|
@@ -4627,18 +4692,18 @@ function compareValues(actual, op, expected) {
|
|
|
4627
4692
|
}
|
|
4628
4693
|
function resolveLinkedPath(ref, docAbsPath, projectRoot) {
|
|
4629
4694
|
const candidates = [
|
|
4630
|
-
|
|
4631
|
-
|
|
4695
|
+
path24.resolve(path24.dirname(docAbsPath), ref),
|
|
4696
|
+
path24.resolve(projectRoot, ref)
|
|
4632
4697
|
];
|
|
4633
4698
|
for (const candidate of candidates) {
|
|
4634
4699
|
if (!candidate.startsWith(projectRoot)) continue;
|
|
4635
|
-
if (
|
|
4700
|
+
if (fs23.existsSync(candidate)) return candidate;
|
|
4636
4701
|
}
|
|
4637
4702
|
return null;
|
|
4638
4703
|
}
|
|
4639
4704
|
function readFrontmatterFromFile(absPath) {
|
|
4640
4705
|
try {
|
|
4641
|
-
const raw =
|
|
4706
|
+
const raw = fs23.readFileSync(absPath, "utf8");
|
|
4642
4707
|
const lines = raw.split("\n");
|
|
4643
4708
|
if (lines[0] !== "---") return {};
|
|
4644
4709
|
let closeIdx = -1;
|
|
@@ -4782,14 +4847,14 @@ function applyCountOp(actual, op, n) {
|
|
|
4782
4847
|
}
|
|
4783
4848
|
}
|
|
4784
4849
|
function evalFileExists(parsed, projectRoot) {
|
|
4785
|
-
const resolved =
|
|
4786
|
-
if (!resolved.startsWith(projectRoot +
|
|
4850
|
+
const resolved = path24.resolve(projectRoot, parsed.path);
|
|
4851
|
+
if (!resolved.startsWith(projectRoot + path24.sep) && resolved !== projectRoot) {
|
|
4787
4852
|
return {
|
|
4788
4853
|
pass: false,
|
|
4789
4854
|
detail: `path '${parsed.path}' resolves outside project root (sandbox violation)`
|
|
4790
4855
|
};
|
|
4791
4856
|
}
|
|
4792
|
-
const exists =
|
|
4857
|
+
const exists = fs23.existsSync(resolved);
|
|
4793
4858
|
return {
|
|
4794
4859
|
pass: exists,
|
|
4795
4860
|
detail: exists ? `${parsed.path} exists` : `${parsed.path} not found`
|
|
@@ -4797,13 +4862,13 @@ function evalFileExists(parsed, projectRoot) {
|
|
|
4797
4862
|
}
|
|
4798
4863
|
function evalLinkTargetExists(parsed, opts) {
|
|
4799
4864
|
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
4800
|
-
const wikiIndexPath = opts?.wikiIndexPath ??
|
|
4865
|
+
const wikiIndexPath = opts?.wikiIndexPath ?? path24.join(projectRoot, ".cleargate", "wiki", "index.md");
|
|
4801
4866
|
if (!wikiIndexPath.startsWith(projectRoot)) {
|
|
4802
4867
|
return { pass: false, detail: "wikiIndexPath resolves outside project root" };
|
|
4803
4868
|
}
|
|
4804
4869
|
let indexContent;
|
|
4805
4870
|
try {
|
|
4806
|
-
indexContent =
|
|
4871
|
+
indexContent = fs23.readFileSync(wikiIndexPath, "utf8");
|
|
4807
4872
|
} catch {
|
|
4808
4873
|
return { pass: false, detail: `wiki index not found at ${wikiIndexPath}` };
|
|
4809
4874
|
}
|
|
@@ -4814,13 +4879,13 @@ function evalLinkTargetExists(parsed, opts) {
|
|
|
4814
4879
|
};
|
|
4815
4880
|
}
|
|
4816
4881
|
function evalStatusOf(parsed, opts, projectRoot) {
|
|
4817
|
-
const wikiIndexPath = opts?.wikiIndexPath ??
|
|
4882
|
+
const wikiIndexPath = opts?.wikiIndexPath ?? path24.join(projectRoot, ".cleargate", "wiki", "index.md");
|
|
4818
4883
|
if (!wikiIndexPath.startsWith(projectRoot)) {
|
|
4819
4884
|
return { pass: false, detail: "wikiIndexPath resolves outside project root" };
|
|
4820
4885
|
}
|
|
4821
4886
|
let indexContent;
|
|
4822
4887
|
try {
|
|
4823
|
-
indexContent =
|
|
4888
|
+
indexContent = fs23.readFileSync(wikiIndexPath, "utf8");
|
|
4824
4889
|
} catch {
|
|
4825
4890
|
return { pass: false, detail: `wiki index not found at ${wikiIndexPath}` };
|
|
4826
4891
|
}
|
|
@@ -4831,7 +4896,7 @@ function evalStatusOf(parsed, opts, projectRoot) {
|
|
|
4831
4896
|
return { pass: false, detail: `[[${parsed.id}]] not found in wiki index` };
|
|
4832
4897
|
}
|
|
4833
4898
|
const rawPath = rowMatch[1].trim();
|
|
4834
|
-
const fullPath =
|
|
4899
|
+
const fullPath = path24.resolve(projectRoot, rawPath);
|
|
4835
4900
|
if (!fullPath.startsWith(projectRoot)) {
|
|
4836
4901
|
return { pass: false, detail: `wiki path for ${parsed.id} resolves outside project root` };
|
|
4837
4902
|
}
|
|
@@ -4848,12 +4913,12 @@ function evalStatusOf(parsed, opts, projectRoot) {
|
|
|
4848
4913
|
}
|
|
4849
4914
|
|
|
4850
4915
|
// src/lib/frontmatter-cache.ts
|
|
4851
|
-
import * as
|
|
4916
|
+
import * as fs24 from "fs/promises";
|
|
4852
4917
|
import yaml5 from "js-yaml";
|
|
4853
4918
|
async function readCachedGate(absPath) {
|
|
4854
4919
|
let raw;
|
|
4855
4920
|
try {
|
|
4856
|
-
raw = await
|
|
4921
|
+
raw = await fs24.readFile(absPath, "utf8");
|
|
4857
4922
|
} catch {
|
|
4858
4923
|
return null;
|
|
4859
4924
|
}
|
|
@@ -4873,7 +4938,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
4873
4938
|
failing_criteria: result.failing_criteria,
|
|
4874
4939
|
last_gate_check: lastGateCheck
|
|
4875
4940
|
};
|
|
4876
|
-
const raw = await
|
|
4941
|
+
const raw = await fs24.readFile(absPath, "utf8");
|
|
4877
4942
|
let fm;
|
|
4878
4943
|
let body;
|
|
4879
4944
|
try {
|
|
@@ -4903,7 +4968,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
4903
4968
|
|
|
4904
4969
|
${body}` : `${fmBlock}
|
|
4905
4970
|
`;
|
|
4906
|
-
await
|
|
4971
|
+
await fs24.writeFile(absPath, newContent, "utf8");
|
|
4907
4972
|
}
|
|
4908
4973
|
function coerceCachedGate(val) {
|
|
4909
4974
|
if (val === void 0 || val === null) return null;
|
|
@@ -4934,7 +4999,7 @@ function coerceCachedGate(val) {
|
|
|
4934
4999
|
|
|
4935
5000
|
// src/commands/gate.ts
|
|
4936
5001
|
function loadGateBlocks(gatesDocPath) {
|
|
4937
|
-
const raw =
|
|
5002
|
+
const raw = fs25.readFileSync(gatesDocPath, "utf8");
|
|
4938
5003
|
const blocks = [];
|
|
4939
5004
|
const fenceRe = /^```yaml\n([\s\S]*?)^```/gm;
|
|
4940
5005
|
let match;
|
|
@@ -4969,14 +5034,14 @@ async function gateCheckHandler(file, opts, cli) {
|
|
|
4969
5034
|
const exitFn = cli?.exit ?? ((code) => process.exit(code));
|
|
4970
5035
|
const cwd = cli?.cwd ?? process.cwd();
|
|
4971
5036
|
const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
|
|
4972
|
-
const absPath =
|
|
4973
|
-
if (!
|
|
5037
|
+
const absPath = path25.isAbsolute(file) ? file : path25.resolve(cwd, file);
|
|
5038
|
+
if (!fs25.existsSync(absPath)) {
|
|
4974
5039
|
stderrFn(`[cleargate gate] error: file not found: ${absPath}`);
|
|
4975
5040
|
return exitFn(1);
|
|
4976
5041
|
}
|
|
4977
5042
|
let raw;
|
|
4978
5043
|
try {
|
|
4979
|
-
raw =
|
|
5044
|
+
raw = fs25.readFileSync(absPath, "utf8");
|
|
4980
5045
|
} catch (err) {
|
|
4981
5046
|
stderrFn(`[cleargate gate] error: cannot read file: ${absPath}`);
|
|
4982
5047
|
return exitFn(1);
|
|
@@ -4995,8 +5060,8 @@ async function gateCheckHandler(file, opts, cli) {
|
|
|
4995
5060
|
return exitFn(1);
|
|
4996
5061
|
}
|
|
4997
5062
|
const projectRoot = cwd;
|
|
4998
|
-
const gatesDocPath = cli?.gatesDocPath ??
|
|
4999
|
-
if (!
|
|
5063
|
+
const gatesDocPath = cli?.gatesDocPath ?? path25.join(projectRoot, ".cleargate", "knowledge", "readiness-gates.md");
|
|
5064
|
+
if (!fs25.existsSync(gatesDocPath)) {
|
|
5000
5065
|
stderrFn(`[cleargate gate] error: readiness-gates.md not found at: ${gatesDocPath}`);
|
|
5001
5066
|
return exitFn(1);
|
|
5002
5067
|
}
|
|
@@ -5069,8 +5134,8 @@ async function gateExplainHandler(file, cli) {
|
|
|
5069
5134
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5070
5135
|
const exitFn = cli?.exit ?? ((code) => process.exit(code));
|
|
5071
5136
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5072
|
-
const absPath =
|
|
5073
|
-
if (!
|
|
5137
|
+
const absPath = path25.isAbsolute(file) ? file : path25.resolve(cwd, file);
|
|
5138
|
+
if (!fs25.existsSync(absPath)) {
|
|
5074
5139
|
stderrFn(`[cleargate gate] error: file not found: ${absPath}`);
|
|
5075
5140
|
return exitFn(1);
|
|
5076
5141
|
}
|
|
@@ -5081,7 +5146,7 @@ async function gateExplainHandler(file, cli) {
|
|
|
5081
5146
|
}
|
|
5082
5147
|
let raw;
|
|
5083
5148
|
try {
|
|
5084
|
-
raw =
|
|
5149
|
+
raw = fs25.readFileSync(absPath, "utf8");
|
|
5085
5150
|
} catch {
|
|
5086
5151
|
stderrFn(`[cleargate gate] error: cannot read file: ${absPath}`);
|
|
5087
5152
|
return exitFn(1);
|
|
@@ -5102,7 +5167,7 @@ async function gateExplainHandler(file, cli) {
|
|
|
5102
5167
|
function resolveRunScriptForGate(opts) {
|
|
5103
5168
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
5104
5169
|
const cwd = opts.cwd ?? process.cwd();
|
|
5105
|
-
return
|
|
5170
|
+
return path25.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
5106
5171
|
}
|
|
5107
5172
|
function gateQaHandler(opts, cli) {
|
|
5108
5173
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -5194,15 +5259,15 @@ function gateRunHandler(name, opts, cli) {
|
|
|
5194
5259
|
}
|
|
5195
5260
|
|
|
5196
5261
|
// src/commands/sprint.ts
|
|
5197
|
-
import * as
|
|
5198
|
-
import * as
|
|
5262
|
+
import * as fs26 from "fs";
|
|
5263
|
+
import * as path26 from "path";
|
|
5199
5264
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
5200
5265
|
import yaml7 from "js-yaml";
|
|
5201
5266
|
var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
|
|
5202
5267
|
function resolveRunScript(opts) {
|
|
5203
5268
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
5204
5269
|
const cwd = opts.cwd ?? process.cwd();
|
|
5205
|
-
return
|
|
5270
|
+
return path26.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
5206
5271
|
}
|
|
5207
5272
|
function defaultExit(code) {
|
|
5208
5273
|
return process.exit(code);
|
|
@@ -5296,8 +5361,8 @@ ${body}`;
|
|
|
5296
5361
|
}
|
|
5297
5362
|
function atomicWriteStr(filePath, content) {
|
|
5298
5363
|
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
5299
|
-
|
|
5300
|
-
|
|
5364
|
+
fs26.writeFileSync(tmp, content, "utf8");
|
|
5365
|
+
fs26.renameSync(tmp, filePath);
|
|
5301
5366
|
}
|
|
5302
5367
|
function deriveSprintBranchForArchive(sprintId) {
|
|
5303
5368
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -5311,7 +5376,7 @@ function stampFile(raw, status, completedAt) {
|
|
|
5311
5376
|
return serializeFileContent(fm, body);
|
|
5312
5377
|
}
|
|
5313
5378
|
function stampSprintClose(sprintPath, now) {
|
|
5314
|
-
const previousContent =
|
|
5379
|
+
const previousContent = fs26.readFileSync(sprintPath, "utf8");
|
|
5315
5380
|
const { fm, body } = parseFileFrontmatter(previousContent);
|
|
5316
5381
|
const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
|
|
5317
5382
|
const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
|
|
@@ -5371,14 +5436,14 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5371
5436
|
if (mode === "v1") {
|
|
5372
5437
|
return printInertAndExit(stdoutFn, exitFn);
|
|
5373
5438
|
}
|
|
5374
|
-
const stateFile =
|
|
5375
|
-
if (!
|
|
5439
|
+
const stateFile = path26.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
|
|
5440
|
+
if (!fs26.existsSync(stateFile)) {
|
|
5376
5441
|
stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
|
|
5377
5442
|
return exitFn(1);
|
|
5378
5443
|
}
|
|
5379
5444
|
let state2;
|
|
5380
5445
|
try {
|
|
5381
|
-
state2 = JSON.parse(
|
|
5446
|
+
state2 = JSON.parse(fs26.readFileSync(stateFile, "utf8"));
|
|
5382
5447
|
} catch (err) {
|
|
5383
5448
|
stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
|
|
5384
5449
|
return exitFn(1);
|
|
@@ -5390,18 +5455,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5390
5455
|
return exitFn(1);
|
|
5391
5456
|
}
|
|
5392
5457
|
const stateStories = state2.stories ?? {};
|
|
5393
|
-
const pendingDir =
|
|
5394
|
-
const archiveDir =
|
|
5458
|
+
const pendingDir = path26.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
5459
|
+
const archiveDir = path26.join(cwd, ".cleargate", "delivery", "archive");
|
|
5395
5460
|
let sprintFile = null;
|
|
5396
|
-
for (const entry of
|
|
5461
|
+
for (const entry of fs26.readdirSync(pendingDir)) {
|
|
5397
5462
|
if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
|
|
5398
|
-
sprintFile =
|
|
5463
|
+
sprintFile = path26.join(pendingDir, entry);
|
|
5399
5464
|
break;
|
|
5400
5465
|
}
|
|
5401
5466
|
}
|
|
5402
5467
|
let epicIds = [];
|
|
5403
|
-
if (sprintFile &&
|
|
5404
|
-
const { fm } = parseFileFrontmatter(
|
|
5468
|
+
if (sprintFile && fs26.existsSync(sprintFile)) {
|
|
5469
|
+
const { fm } = parseFileFrontmatter(fs26.readFileSync(sprintFile, "utf8"));
|
|
5405
5470
|
const epics = fm["epics"];
|
|
5406
5471
|
if (Array.isArray(epics)) {
|
|
5407
5472
|
epicIds = epics.map(String);
|
|
@@ -5411,15 +5476,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5411
5476
|
if (sprintFile) {
|
|
5412
5477
|
plan.push({
|
|
5413
5478
|
src: sprintFile,
|
|
5414
|
-
destName:
|
|
5479
|
+
destName: path26.basename(sprintFile),
|
|
5415
5480
|
status: "Completed"
|
|
5416
5481
|
});
|
|
5417
5482
|
}
|
|
5418
5483
|
for (const epicId of epicIds) {
|
|
5419
|
-
for (const entry of
|
|
5484
|
+
for (const entry of fs26.readdirSync(pendingDir)) {
|
|
5420
5485
|
if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
|
|
5421
5486
|
plan.push({
|
|
5422
|
-
src:
|
|
5487
|
+
src: path26.join(pendingDir, entry),
|
|
5423
5488
|
destName: entry,
|
|
5424
5489
|
status: "Approved"
|
|
5425
5490
|
});
|
|
@@ -5428,10 +5493,10 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5428
5493
|
}
|
|
5429
5494
|
const storyKeys = storyKeysForEpic(stateStories, epicId);
|
|
5430
5495
|
for (const storyId of storyKeys) {
|
|
5431
|
-
for (const entry of
|
|
5496
|
+
for (const entry of fs26.readdirSync(pendingDir)) {
|
|
5432
5497
|
if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
|
|
5433
5498
|
plan.push({
|
|
5434
|
-
src:
|
|
5499
|
+
src: path26.join(pendingDir, entry),
|
|
5435
5500
|
destName: entry,
|
|
5436
5501
|
status: "Done"
|
|
5437
5502
|
});
|
|
@@ -5443,13 +5508,13 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5443
5508
|
const storyIdsInState = new Set(Object.keys(stateStories));
|
|
5444
5509
|
const planSrcs = new Set(plan.map((p) => p.src));
|
|
5445
5510
|
const orphans = [];
|
|
5446
|
-
for (const entry of
|
|
5511
|
+
for (const entry of fs26.readdirSync(pendingDir)) {
|
|
5447
5512
|
if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
|
|
5448
|
-
const candidate =
|
|
5513
|
+
const candidate = path26.join(pendingDir, entry);
|
|
5449
5514
|
if (planSrcs.has(candidate)) continue;
|
|
5450
5515
|
let raw;
|
|
5451
5516
|
try {
|
|
5452
|
-
raw =
|
|
5517
|
+
raw = fs26.readFileSync(candidate, "utf8");
|
|
5453
5518
|
} catch {
|
|
5454
5519
|
continue;
|
|
5455
5520
|
}
|
|
@@ -5466,18 +5531,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5466
5531
|
}
|
|
5467
5532
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5468
5533
|
const sprintBranch = deriveSprintBranchForArchive(opts.sprintId);
|
|
5469
|
-
const activePath =
|
|
5534
|
+
const activePath = path26.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
5470
5535
|
if (opts.dryRun) {
|
|
5471
5536
|
stdoutFn(`[dry-run] Sprint archive plan for ${opts.sprintId}:`);
|
|
5472
5537
|
stdoutFn(` Sprint branch: ${sprintBranch}`);
|
|
5473
5538
|
stdoutFn(` Files to archive (${plan.length}):`);
|
|
5474
5539
|
for (const entry of plan) {
|
|
5475
5540
|
stdoutFn(
|
|
5476
|
-
` ${
|
|
5541
|
+
` ${path26.basename(entry.src)} \u2192 archive/${entry.destName} [stamp: status=${entry.status}, completed_at=<now>]`
|
|
5477
5542
|
);
|
|
5478
5543
|
}
|
|
5479
5544
|
if (orphans.length > 0) {
|
|
5480
|
-
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) =>
|
|
5545
|
+
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) => path26.basename(o)).join(", ")}`);
|
|
5481
5546
|
}
|
|
5482
5547
|
stdoutFn(` .active \u2192 "" (truncate)`);
|
|
5483
5548
|
stdoutFn(` git checkout main`);
|
|
@@ -5486,9 +5551,9 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5486
5551
|
return exitFn(0);
|
|
5487
5552
|
}
|
|
5488
5553
|
let sprintFileSnapshot = null;
|
|
5489
|
-
const wikiRoot =
|
|
5490
|
-
const wikiInitialised =
|
|
5491
|
-
if (sprintFile &&
|
|
5554
|
+
const wikiRoot = path26.join(cwd, ".cleargate", "wiki");
|
|
5555
|
+
const wikiInitialised = fs26.existsSync(wikiRoot);
|
|
5556
|
+
if (sprintFile && fs26.existsSync(sprintFile)) {
|
|
5492
5557
|
const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
|
|
5493
5558
|
sprintFileSnapshot = previousContent;
|
|
5494
5559
|
if (wikiInitialised) {
|
|
@@ -5510,15 +5575,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5510
5575
|
}
|
|
5511
5576
|
}
|
|
5512
5577
|
for (const entry of plan) {
|
|
5513
|
-
if (!
|
|
5578
|
+
if (!fs26.existsSync(entry.src)) {
|
|
5514
5579
|
stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
|
|
5515
5580
|
continue;
|
|
5516
5581
|
}
|
|
5517
|
-
const raw =
|
|
5582
|
+
const raw = fs26.readFileSync(entry.src, "utf8");
|
|
5518
5583
|
const stamped = stampFile(raw, entry.status, completedAt);
|
|
5519
|
-
const dest =
|
|
5584
|
+
const dest = path26.join(archiveDir, entry.destName);
|
|
5520
5585
|
atomicWriteStr(entry.src, stamped);
|
|
5521
|
-
|
|
5586
|
+
fs26.renameSync(entry.src, dest);
|
|
5522
5587
|
stdoutFn(`archived: ${entry.destName}`);
|
|
5523
5588
|
}
|
|
5524
5589
|
try {
|
|
@@ -5560,8 +5625,8 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5560
5625
|
}
|
|
5561
5626
|
|
|
5562
5627
|
// src/commands/story.ts
|
|
5563
|
-
import * as
|
|
5564
|
-
import * as
|
|
5628
|
+
import * as fs27 from "fs";
|
|
5629
|
+
import * as path27 from "path";
|
|
5565
5630
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
5566
5631
|
function defaultExit2(code) {
|
|
5567
5632
|
return process.exit(code);
|
|
@@ -5569,7 +5634,7 @@ function defaultExit2(code) {
|
|
|
5569
5634
|
function resolveRunScript2(opts) {
|
|
5570
5635
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
5571
5636
|
const cwd = opts.cwd ?? process.cwd();
|
|
5572
|
-
return
|
|
5637
|
+
return path27.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
5573
5638
|
}
|
|
5574
5639
|
function deriveSprintBranch(sprintId) {
|
|
5575
5640
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -5578,11 +5643,11 @@ function deriveSprintBranch(sprintId) {
|
|
|
5578
5643
|
}
|
|
5579
5644
|
function atomicWriteString(filePath, text) {
|
|
5580
5645
|
const tmpFile = `${filePath}.tmp.${process.pid}`;
|
|
5581
|
-
|
|
5582
|
-
|
|
5646
|
+
fs27.writeFileSync(tmpFile, text, "utf8");
|
|
5647
|
+
fs27.renameSync(tmpFile, filePath);
|
|
5583
5648
|
}
|
|
5584
5649
|
function stateJsonPath(cwd, sprintId) {
|
|
5585
|
-
return
|
|
5650
|
+
return path27.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
|
|
5586
5651
|
}
|
|
5587
5652
|
function storyStartHandler(opts, cli) {
|
|
5588
5653
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -5599,7 +5664,7 @@ function storyStartHandler(opts, cli) {
|
|
|
5599
5664
|
return printInertAndExit(stdoutFn, exitFn);
|
|
5600
5665
|
}
|
|
5601
5666
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
5602
|
-
const worktreePath =
|
|
5667
|
+
const worktreePath = path27.join(cwd, ".worktrees", opts.storyId);
|
|
5603
5668
|
const storyBranch = `story/${opts.storyId}`;
|
|
5604
5669
|
const step1 = spawnFn(
|
|
5605
5670
|
"git",
|
|
@@ -5631,13 +5696,13 @@ function storyStartHandler(opts, cli) {
|
|
|
5631
5696
|
return exitFn(step2.status ?? 1);
|
|
5632
5697
|
}
|
|
5633
5698
|
const stateFile = stateJsonPath(cwd, sprintId);
|
|
5634
|
-
if (!
|
|
5699
|
+
if (!fs27.existsSync(stateFile)) {
|
|
5635
5700
|
stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
|
|
5636
5701
|
return exitFn(1);
|
|
5637
5702
|
}
|
|
5638
5703
|
let state2;
|
|
5639
5704
|
try {
|
|
5640
|
-
state2 = JSON.parse(
|
|
5705
|
+
state2 = JSON.parse(fs27.readFileSync(stateFile, "utf8"));
|
|
5641
5706
|
} catch (err) {
|
|
5642
5707
|
stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
|
|
5643
5708
|
return exitFn(1);
|
|
@@ -5678,7 +5743,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
5678
5743
|
}
|
|
5679
5744
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
5680
5745
|
const storyBranch = `story/${opts.storyId}`;
|
|
5681
|
-
const worktreeRel =
|
|
5746
|
+
const worktreeRel = path27.join(".worktrees", opts.storyId);
|
|
5682
5747
|
const step1 = spawnFn(
|
|
5683
5748
|
"git",
|
|
5684
5749
|
["rev-list", "--count", `${sprintBranch}..${storyBranch}`],
|
|
@@ -5775,7 +5840,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
5775
5840
|
}
|
|
5776
5841
|
|
|
5777
5842
|
// src/commands/state.ts
|
|
5778
|
-
import * as
|
|
5843
|
+
import * as path28 from "path";
|
|
5779
5844
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
5780
5845
|
function defaultExit3(code) {
|
|
5781
5846
|
return process.exit(code);
|
|
@@ -5783,7 +5848,7 @@ function defaultExit3(code) {
|
|
|
5783
5848
|
function resolveRunScript3(opts) {
|
|
5784
5849
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
5785
5850
|
const cwd = opts.cwd ?? process.cwd();
|
|
5786
|
-
return
|
|
5851
|
+
return path28.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
5787
5852
|
}
|
|
5788
5853
|
function stateUpdateHandler(opts, cli) {
|
|
5789
5854
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -5839,20 +5904,20 @@ function stateValidateHandler(opts, cli) {
|
|
|
5839
5904
|
}
|
|
5840
5905
|
|
|
5841
5906
|
// src/commands/stamp-tokens.ts
|
|
5842
|
-
import * as
|
|
5843
|
-
import * as
|
|
5907
|
+
import * as fs29 from "fs";
|
|
5908
|
+
import * as path30 from "path";
|
|
5844
5909
|
|
|
5845
5910
|
// src/lib/ledger-reader.ts
|
|
5846
|
-
import * as
|
|
5847
|
-
import * as
|
|
5911
|
+
import * as fs28 from "fs";
|
|
5912
|
+
import * as path29 from "path";
|
|
5848
5913
|
function findSprintRunsRoot(startDir) {
|
|
5849
5914
|
let dir = startDir;
|
|
5850
5915
|
while (true) {
|
|
5851
|
-
const candidate =
|
|
5852
|
-
if (
|
|
5916
|
+
const candidate = path29.join(dir, ".cleargate", "sprint-runs");
|
|
5917
|
+
if (fs28.existsSync(candidate)) {
|
|
5853
5918
|
return candidate;
|
|
5854
5919
|
}
|
|
5855
|
-
const parent =
|
|
5920
|
+
const parent = path29.dirname(dir);
|
|
5856
5921
|
if (parent === dir) {
|
|
5857
5922
|
return null;
|
|
5858
5923
|
}
|
|
@@ -5905,13 +5970,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
5905
5970
|
}
|
|
5906
5971
|
sprintRunsRoot = found;
|
|
5907
5972
|
}
|
|
5908
|
-
if (!
|
|
5973
|
+
if (!fs28.existsSync(sprintRunsRoot)) {
|
|
5909
5974
|
return [];
|
|
5910
5975
|
}
|
|
5911
5976
|
let ledgerFiles;
|
|
5912
5977
|
try {
|
|
5913
|
-
const entries =
|
|
5914
|
-
ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
5978
|
+
const entries = fs28.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
5979
|
+
ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path29.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs28.existsSync(f));
|
|
5915
5980
|
} catch {
|
|
5916
5981
|
return [];
|
|
5917
5982
|
}
|
|
@@ -5919,7 +5984,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
5919
5984
|
for (const ledgerFile of ledgerFiles) {
|
|
5920
5985
|
let content;
|
|
5921
5986
|
try {
|
|
5922
|
-
content =
|
|
5987
|
+
content = fs28.readFileSync(ledgerFile, "utf-8");
|
|
5923
5988
|
} catch {
|
|
5924
5989
|
continue;
|
|
5925
5990
|
}
|
|
@@ -5970,7 +6035,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
5970
6035
|
});
|
|
5971
6036
|
const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
|
|
5972
6037
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5973
|
-
const absPath =
|
|
6038
|
+
const absPath = path30.isAbsolute(file) ? file : path30.resolve(cwd, file);
|
|
5974
6039
|
if (/\/\.cleargate\/delivery\/archive\//.test(absPath)) {
|
|
5975
6040
|
stdoutFn(`[frozen] ${absPath}`);
|
|
5976
6041
|
exitFn(0);
|
|
@@ -5978,7 +6043,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
5978
6043
|
}
|
|
5979
6044
|
let rawContent;
|
|
5980
6045
|
try {
|
|
5981
|
-
rawContent =
|
|
6046
|
+
rawContent = fs29.readFileSync(absPath, "utf-8");
|
|
5982
6047
|
} catch {
|
|
5983
6048
|
stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
|
|
5984
6049
|
exitFn(1);
|
|
@@ -6050,7 +6115,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
6050
6115
|
return;
|
|
6051
6116
|
}
|
|
6052
6117
|
try {
|
|
6053
|
-
|
|
6118
|
+
fs29.writeFileSync(absPath, serialized, "utf-8");
|
|
6054
6119
|
} catch {
|
|
6055
6120
|
stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
|
|
6056
6121
|
exitFn(1);
|
|
@@ -6067,7 +6132,7 @@ function extractWorkItemId(fm, absPath) {
|
|
|
6067
6132
|
return val.trim();
|
|
6068
6133
|
}
|
|
6069
6134
|
}
|
|
6070
|
-
const basename12 =
|
|
6135
|
+
const basename12 = path30.basename(absPath);
|
|
6071
6136
|
const match = basename12.match(/^(STORY|EPIC|PROPOSAL|CR|BUG)-\d+(-\d+)?/i);
|
|
6072
6137
|
if (match) {
|
|
6073
6138
|
return match[0].toUpperCase();
|
|
@@ -6172,7 +6237,7 @@ ${body}`;
|
|
|
6172
6237
|
|
|
6173
6238
|
// src/commands/upgrade.ts
|
|
6174
6239
|
import * as fsp from "fs/promises";
|
|
6175
|
-
import * as
|
|
6240
|
+
import * as path31 from "path";
|
|
6176
6241
|
|
|
6177
6242
|
// src/lib/claude-md-surgery.ts
|
|
6178
6243
|
var CLEARGATE_START = "<!-- CLEARGATE:START -->";
|
|
@@ -6318,7 +6383,7 @@ async function writeAtomic2(filePath, content) {
|
|
|
6318
6383
|
await fsp.rename(tmpPath, filePath);
|
|
6319
6384
|
}
|
|
6320
6385
|
async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
6321
|
-
const snapshotPath =
|
|
6386
|
+
const snapshotPath = path31.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
6322
6387
|
let snapshot;
|
|
6323
6388
|
try {
|
|
6324
6389
|
const raw = await fsp.readFile(snapshotPath, "utf-8");
|
|
@@ -6335,17 +6400,17 @@ async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
|
6335
6400
|
await writeAtomic2(snapshotPath, JSON.stringify(updated, null, 2) + "\n");
|
|
6336
6401
|
}
|
|
6337
6402
|
function isClaudeMd(filePath) {
|
|
6338
|
-
return
|
|
6403
|
+
return path31.basename(filePath) === "CLAUDE.md";
|
|
6339
6404
|
}
|
|
6340
6405
|
function isSettingsJson(filePath) {
|
|
6341
|
-
return
|
|
6406
|
+
return path31.basename(filePath) === "settings.json" && filePath.includes(".claude");
|
|
6342
6407
|
}
|
|
6343
6408
|
async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
6344
|
-
const targetPath =
|
|
6345
|
-
const sourcePath =
|
|
6409
|
+
const targetPath = path31.join(projectRoot, entry.path);
|
|
6410
|
+
const sourcePath = path31.join(packageRoot, entry.path);
|
|
6346
6411
|
try {
|
|
6347
6412
|
const pkgContent = await fsp.readFile(sourcePath, "utf-8");
|
|
6348
|
-
await fsp.mkdir(
|
|
6413
|
+
await fsp.mkdir(path31.dirname(targetPath), { recursive: true });
|
|
6349
6414
|
await writeAtomic2(targetPath, pkgContent);
|
|
6350
6415
|
await updateSnapshotEntry(projectRoot, entry.path, entry.sha256);
|
|
6351
6416
|
stdout(`[always] overwritten: ${entry.path}`);
|
|
@@ -6355,8 +6420,8 @@ async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
|
6355
6420
|
}
|
|
6356
6421
|
async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, currentSha, flags, opts) {
|
|
6357
6422
|
const { stdout, stderr, promptMergeChoiceFn, openInEditorFn, stdin } = opts;
|
|
6358
|
-
const targetPath =
|
|
6359
|
-
const sourcePath =
|
|
6423
|
+
const targetPath = path31.join(projectRoot, entry.path);
|
|
6424
|
+
const sourcePath = path31.join(packageRoot, entry.path);
|
|
6360
6425
|
let ours = "";
|
|
6361
6426
|
let theirs = "";
|
|
6362
6427
|
try {
|
|
@@ -6419,7 +6484,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
6419
6484
|
mergedContent = theirs;
|
|
6420
6485
|
}
|
|
6421
6486
|
}
|
|
6422
|
-
await fsp.mkdir(
|
|
6487
|
+
await fsp.mkdir(path31.dirname(targetPath), { recursive: true });
|
|
6423
6488
|
await writeAtomic2(targetPath, mergedContent);
|
|
6424
6489
|
const newSha2 = hashNormalized(mergedContent);
|
|
6425
6490
|
await updateSnapshotEntry(projectRoot, entry.path, newSha2);
|
|
@@ -6431,7 +6496,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
6431
6496
|
${ours}=======
|
|
6432
6497
|
${theirs}>>>>>>> theirs (upstream)
|
|
6433
6498
|
`;
|
|
6434
|
-
await fsp.mkdir(
|
|
6499
|
+
await fsp.mkdir(path31.dirname(mergeFilePath), { recursive: true });
|
|
6435
6500
|
await writeAtomic2(mergeFilePath, conflictContent);
|
|
6436
6501
|
try {
|
|
6437
6502
|
const result = await openInEditorFn(mergeFilePath);
|
|
@@ -6565,9 +6630,9 @@ async function upgradeHandler(flags, cli) {
|
|
|
6565
6630
|
}
|
|
6566
6631
|
|
|
6567
6632
|
// src/commands/uninstall.ts
|
|
6568
|
-
import * as
|
|
6633
|
+
import * as fs30 from "fs";
|
|
6569
6634
|
import * as fsp2 from "fs/promises";
|
|
6570
|
-
import * as
|
|
6635
|
+
import * as path32 from "path";
|
|
6571
6636
|
import { execSync as execSync2 } from "child_process";
|
|
6572
6637
|
var USER_ARTIFACT_TIERS = ["user-artifact"];
|
|
6573
6638
|
var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
|
|
@@ -6593,10 +6658,10 @@ function shouldPreserve(entry, preserveSet, removeSet) {
|
|
|
6593
6658
|
return false;
|
|
6594
6659
|
}
|
|
6595
6660
|
function resolveProjectName(target) {
|
|
6596
|
-
const pkgPath =
|
|
6597
|
-
if (
|
|
6661
|
+
const pkgPath = path32.join(target, "package.json");
|
|
6662
|
+
if (fs30.existsSync(pkgPath)) {
|
|
6598
6663
|
try {
|
|
6599
|
-
const raw =
|
|
6664
|
+
const raw = fs30.readFileSync(pkgPath, "utf-8");
|
|
6600
6665
|
const parsed = JSON.parse(raw);
|
|
6601
6666
|
if (parsed.name && typeof parsed.name === "string") {
|
|
6602
6667
|
return parsed.name;
|
|
@@ -6604,7 +6669,7 @@ function resolveProjectName(target) {
|
|
|
6604
6669
|
} catch {
|
|
6605
6670
|
}
|
|
6606
6671
|
}
|
|
6607
|
-
return
|
|
6672
|
+
return path32.basename(target);
|
|
6608
6673
|
}
|
|
6609
6674
|
function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
6610
6675
|
const run = gitRunner ?? ((args) => {
|
|
@@ -6633,8 +6698,8 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
|
6633
6698
|
return changedFiles.filter((f) => manifestSet.has(f));
|
|
6634
6699
|
}
|
|
6635
6700
|
async function removeFromPackageJson(target, dryRun) {
|
|
6636
|
-
const pkgPath =
|
|
6637
|
-
if (!
|
|
6701
|
+
const pkgPath = path32.join(target, "package.json");
|
|
6702
|
+
if (!fs30.existsSync(pkgPath)) return false;
|
|
6638
6703
|
let raw;
|
|
6639
6704
|
try {
|
|
6640
6705
|
raw = await fsp2.readFile(pkgPath, "utf-8");
|
|
@@ -6675,7 +6740,7 @@ async function removeFile(filePath) {
|
|
|
6675
6740
|
}
|
|
6676
6741
|
async function removeDir(dirPath) {
|
|
6677
6742
|
try {
|
|
6678
|
-
|
|
6743
|
+
fs30.rmSync(dirPath, { recursive: true, force: true });
|
|
6679
6744
|
} catch {
|
|
6680
6745
|
}
|
|
6681
6746
|
}
|
|
@@ -6695,12 +6760,12 @@ async function uninstallHandler(opts) {
|
|
|
6695
6760
|
for (const t of FRAMEWORK_TIERS) removeSet.add(t);
|
|
6696
6761
|
for (const u of USER_ARTIFACT_TIERS) removeSet.add(u);
|
|
6697
6762
|
}
|
|
6698
|
-
const target = opts.path ?
|
|
6699
|
-
const cleargateDir =
|
|
6700
|
-
const manifestPath =
|
|
6701
|
-
const uninstalledPath =
|
|
6702
|
-
if (!
|
|
6703
|
-
if (
|
|
6763
|
+
const target = opts.path ? path32.resolve(opts.path) : cwd;
|
|
6764
|
+
const cleargateDir = path32.join(target, ".cleargate");
|
|
6765
|
+
const manifestPath = path32.join(cleargateDir, ".install-manifest.json");
|
|
6766
|
+
const uninstalledPath = path32.join(cleargateDir, ".uninstalled");
|
|
6767
|
+
if (!fs30.existsSync(manifestPath)) {
|
|
6768
|
+
if (fs30.existsSync(uninstalledPath)) {
|
|
6704
6769
|
stdout("already uninstalled");
|
|
6705
6770
|
exit(0);
|
|
6706
6771
|
return;
|
|
@@ -6709,7 +6774,7 @@ async function uninstallHandler(opts) {
|
|
|
6709
6774
|
exit(0);
|
|
6710
6775
|
return;
|
|
6711
6776
|
}
|
|
6712
|
-
if (
|
|
6777
|
+
if (fs30.existsSync(uninstalledPath) && !fs30.existsSync(manifestPath)) {
|
|
6713
6778
|
stdout("already uninstalled");
|
|
6714
6779
|
exit(0);
|
|
6715
6780
|
return;
|
|
@@ -6731,10 +6796,10 @@ async function uninstallHandler(opts) {
|
|
|
6731
6796
|
return;
|
|
6732
6797
|
}
|
|
6733
6798
|
}
|
|
6734
|
-
const claudeMdPath =
|
|
6799
|
+
const claudeMdPath = path32.join(target, "CLAUDE.md");
|
|
6735
6800
|
let claudeMdContent = null;
|
|
6736
|
-
if (
|
|
6737
|
-
claudeMdContent =
|
|
6801
|
+
if (fs30.existsSync(claudeMdPath)) {
|
|
6802
|
+
claudeMdContent = fs30.readFileSync(claudeMdPath, "utf-8");
|
|
6738
6803
|
if (!claudeMdContent.includes(CLEARGATE_START)) {
|
|
6739
6804
|
stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
|
|
6740
6805
|
exit(1);
|
|
@@ -6750,8 +6815,8 @@ async function uninstallHandler(opts) {
|
|
|
6750
6815
|
const toPreserve = [];
|
|
6751
6816
|
const toSkip = [];
|
|
6752
6817
|
for (const entry of snapshot.files) {
|
|
6753
|
-
const filePath =
|
|
6754
|
-
if (!
|
|
6818
|
+
const filePath = path32.join(target, entry.path);
|
|
6819
|
+
if (!fs30.existsSync(filePath)) {
|
|
6755
6820
|
toSkip.push(entry);
|
|
6756
6821
|
continue;
|
|
6757
6822
|
}
|
|
@@ -6808,7 +6873,7 @@ async function uninstallHandler(opts) {
|
|
|
6808
6873
|
const removedPaths = [];
|
|
6809
6874
|
const preservedPaths = [];
|
|
6810
6875
|
for (const entry of toRemove) {
|
|
6811
|
-
const filePath =
|
|
6876
|
+
const filePath = path32.join(target, entry.path);
|
|
6812
6877
|
await removeFile(filePath);
|
|
6813
6878
|
removedPaths.push(entry.path);
|
|
6814
6879
|
}
|
|
@@ -6824,10 +6889,10 @@ async function uninstallHandler(opts) {
|
|
|
6824
6889
|
stderr(`Warning: could not strip CLAUDE.md block: ${err.message}`);
|
|
6825
6890
|
}
|
|
6826
6891
|
}
|
|
6827
|
-
const settingsPath =
|
|
6828
|
-
if (
|
|
6892
|
+
const settingsPath = path32.join(target, ".claude", "settings.json");
|
|
6893
|
+
if (fs30.existsSync(settingsPath)) {
|
|
6829
6894
|
try {
|
|
6830
|
-
const raw =
|
|
6895
|
+
const raw = fs30.readFileSync(settingsPath, "utf-8");
|
|
6831
6896
|
const settings = JSON.parse(raw);
|
|
6832
6897
|
const cleaned = removeClearGateHooks(settings);
|
|
6833
6898
|
await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
|
|
@@ -6842,7 +6907,7 @@ async function uninstallHandler(opts) {
|
|
|
6842
6907
|
stdout("Removed @cleargate/cli from package.json. Run `npm install` to update package-lock.json.");
|
|
6843
6908
|
}
|
|
6844
6909
|
await removeFile(manifestPath);
|
|
6845
|
-
await removeFile(
|
|
6910
|
+
await removeFile(path32.join(cleargateDir, ".drift-state.json"));
|
|
6846
6911
|
const marker = {
|
|
6847
6912
|
uninstalled_at: now().toISOString(),
|
|
6848
6913
|
prior_version: snapshot.cleargate_version,
|
|
@@ -6866,29 +6931,29 @@ async function uninstallHandler(opts) {
|
|
|
6866
6931
|
|
|
6867
6932
|
// src/commands/sync.ts
|
|
6868
6933
|
import * as fsPromises8 from "fs/promises";
|
|
6869
|
-
import * as
|
|
6934
|
+
import * as path40 from "path";
|
|
6870
6935
|
|
|
6871
6936
|
// src/lib/sync-log.ts
|
|
6872
|
-
import * as
|
|
6937
|
+
import * as fs31 from "fs";
|
|
6873
6938
|
import * as fsPromises2 from "fs/promises";
|
|
6874
|
-
import * as
|
|
6939
|
+
import * as path33 from "path";
|
|
6875
6940
|
function resolveActiveSprintDir(projectRoot, _opts) {
|
|
6876
|
-
const sprintRunsRoot =
|
|
6877
|
-
const offSprint =
|
|
6878
|
-
if (!
|
|
6879
|
-
|
|
6880
|
-
|
|
6941
|
+
const sprintRunsRoot = path33.join(projectRoot, ".cleargate", "sprint-runs");
|
|
6942
|
+
const offSprint = path33.join(sprintRunsRoot, "_off-sprint");
|
|
6943
|
+
if (!fs31.existsSync(sprintRunsRoot)) {
|
|
6944
|
+
fs31.mkdirSync(sprintRunsRoot, { recursive: true });
|
|
6945
|
+
fs31.mkdirSync(offSprint, { recursive: true });
|
|
6881
6946
|
return offSprint;
|
|
6882
6947
|
}
|
|
6883
|
-
const entries =
|
|
6948
|
+
const entries = fs31.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
6884
6949
|
const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
|
|
6885
|
-
const fullPath =
|
|
6886
|
-
const stat =
|
|
6950
|
+
const fullPath = path33.join(sprintRunsRoot, e.name);
|
|
6951
|
+
const stat = fs31.statSync(fullPath);
|
|
6887
6952
|
return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
|
|
6888
6953
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
6889
6954
|
if (sprintDirs.length === 0) {
|
|
6890
|
-
if (!
|
|
6891
|
-
|
|
6955
|
+
if (!fs31.existsSync(offSprint)) {
|
|
6956
|
+
fs31.mkdirSync(offSprint, { recursive: true });
|
|
6892
6957
|
}
|
|
6893
6958
|
return offSprint;
|
|
6894
6959
|
}
|
|
@@ -6899,7 +6964,7 @@ function redactDetail(detail) {
|
|
|
6899
6964
|
return detail.replace(/eyJ[A-Za-z0-9._-]+/g, "[REDACTED]");
|
|
6900
6965
|
}
|
|
6901
6966
|
async function appendSyncLog(sprintRoot, entry) {
|
|
6902
|
-
const logPath =
|
|
6967
|
+
const logPath = path33.join(sprintRoot, "sync-log.jsonl");
|
|
6903
6968
|
await fsPromises2.mkdir(sprintRoot, { recursive: true });
|
|
6904
6969
|
const safeEntry = {
|
|
6905
6970
|
...entry,
|
|
@@ -6909,7 +6974,7 @@ async function appendSyncLog(sprintRoot, entry) {
|
|
|
6909
6974
|
await fsPromises2.appendFile(logPath, line, { encoding: "utf8" });
|
|
6910
6975
|
}
|
|
6911
6976
|
async function readSyncLog(sprintRoot, filters) {
|
|
6912
|
-
const logPath =
|
|
6977
|
+
const logPath = path33.join(sprintRoot, "sync-log.jsonl");
|
|
6913
6978
|
let raw;
|
|
6914
6979
|
try {
|
|
6915
6980
|
raw = await fsPromises2.readFile(logPath, "utf8");
|
|
@@ -7011,9 +7076,9 @@ function classify2(local, remote, since) {
|
|
|
7011
7076
|
}
|
|
7012
7077
|
|
|
7013
7078
|
// src/lib/merge-helper.ts
|
|
7014
|
-
import { promises as
|
|
7079
|
+
import { promises as fs32 } from "fs";
|
|
7015
7080
|
import * as os4 from "os";
|
|
7016
|
-
import * as
|
|
7081
|
+
import * as path34 from "path";
|
|
7017
7082
|
function promptFourChoice(opts) {
|
|
7018
7083
|
const { stdin, stdout } = opts;
|
|
7019
7084
|
stdout("[k]eep mine / [t]ake theirs / [e]dit in $EDITOR / [a]bort: ");
|
|
@@ -7083,7 +7148,7 @@ async function promptThreeWayMerge(opts) {
|
|
|
7083
7148
|
case "a":
|
|
7084
7149
|
return { resolution: "aborted", body: local };
|
|
7085
7150
|
case "e": {
|
|
7086
|
-
const tmpFile =
|
|
7151
|
+
const tmpFile = path34.join(os4.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
|
|
7087
7152
|
const markerContent = `<<<<<<< local
|
|
7088
7153
|
${local}
|
|
7089
7154
|
=======
|
|
@@ -7091,16 +7156,16 @@ ${remote}
|
|
|
7091
7156
|
>>>>>>> remote
|
|
7092
7157
|
`;
|
|
7093
7158
|
try {
|
|
7094
|
-
await
|
|
7159
|
+
await fs32.writeFile(tmpFile, markerContent, "utf-8");
|
|
7095
7160
|
await openInEditor(tmpFile, { editor: editor ?? process.env["EDITOR"] ?? "vi" });
|
|
7096
|
-
const edited = await
|
|
7161
|
+
const edited = await fs32.readFile(tmpFile, "utf-8");
|
|
7097
7162
|
if (containsConflictMarkers(edited)) {
|
|
7098
7163
|
stdout("File still contains conflict markers \u2014 please resolve all conflicts.\n");
|
|
7099
7164
|
continue;
|
|
7100
7165
|
}
|
|
7101
7166
|
return { resolution: "edited", body: edited };
|
|
7102
7167
|
} finally {
|
|
7103
|
-
await
|
|
7168
|
+
await fs32.unlink(tmpFile).catch(() => {
|
|
7104
7169
|
});
|
|
7105
7170
|
}
|
|
7106
7171
|
}
|
|
@@ -7177,11 +7242,11 @@ function createMcpClient(opts) {
|
|
|
7177
7242
|
|
|
7178
7243
|
// src/lib/intake.ts
|
|
7179
7244
|
import * as fsPromises4 from "fs/promises";
|
|
7180
|
-
import * as
|
|
7245
|
+
import * as path36 from "path";
|
|
7181
7246
|
|
|
7182
7247
|
// src/lib/slug.ts
|
|
7183
7248
|
import * as fsPromises3 from "fs/promises";
|
|
7184
|
-
import * as
|
|
7249
|
+
import * as path35 from "path";
|
|
7185
7250
|
function slugify(title, max = 40) {
|
|
7186
7251
|
const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
7187
7252
|
const lowered = normalized.toLowerCase();
|
|
@@ -7196,8 +7261,8 @@ function slugify(title, max = 40) {
|
|
|
7196
7261
|
var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
|
|
7197
7262
|
async function nextProposalId(projectRoot) {
|
|
7198
7263
|
const dirs = [
|
|
7199
|
-
|
|
7200
|
-
|
|
7264
|
+
path35.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
7265
|
+
path35.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
7201
7266
|
];
|
|
7202
7267
|
let maxN = 0;
|
|
7203
7268
|
for (const dir of dirs) {
|
|
@@ -7209,7 +7274,7 @@ async function nextProposalId(projectRoot) {
|
|
|
7209
7274
|
}
|
|
7210
7275
|
for (const entry of entries) {
|
|
7211
7276
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7212
|
-
const fullPath =
|
|
7277
|
+
const fullPath = path35.join(dir, entry.name);
|
|
7213
7278
|
try {
|
|
7214
7279
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
7215
7280
|
const fmEnd = extractFrontmatterBlock(raw);
|
|
@@ -7227,8 +7292,8 @@ async function nextProposalId(projectRoot) {
|
|
|
7227
7292
|
}
|
|
7228
7293
|
async function findByRemoteId(projectRoot, remoteId) {
|
|
7229
7294
|
const dirs = [
|
|
7230
|
-
|
|
7231
|
-
|
|
7295
|
+
path35.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
7296
|
+
path35.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
7232
7297
|
];
|
|
7233
7298
|
const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7234
7299
|
const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
|
|
@@ -7241,7 +7306,7 @@ async function findByRemoteId(projectRoot, remoteId) {
|
|
|
7241
7306
|
}
|
|
7242
7307
|
for (const entry of entries) {
|
|
7243
7308
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7244
|
-
const fullPath =
|
|
7309
|
+
const fullPath = path35.join(dir, entry.name);
|
|
7245
7310
|
try {
|
|
7246
7311
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
7247
7312
|
const fm = extractFrontmatterBlock(raw);
|
|
@@ -7278,7 +7343,7 @@ async function runIntakeBranch(opts) {
|
|
|
7278
7343
|
labelFilter = "cleargate:proposal",
|
|
7279
7344
|
now = () => (/* @__PURE__ */ new Date()).toISOString()
|
|
7280
7345
|
} = opts;
|
|
7281
|
-
const pendingSyncDir =
|
|
7346
|
+
const pendingSyncDir = path36.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
7282
7347
|
let remoteItems = [];
|
|
7283
7348
|
try {
|
|
7284
7349
|
remoteItems = await mcp.call(
|
|
@@ -7309,7 +7374,7 @@ async function runIntakeBranch(opts) {
|
|
|
7309
7374
|
const slug2 = slugify(item.title ?? "untitled");
|
|
7310
7375
|
const num2 = proposalId2.replace("PROP-", "");
|
|
7311
7376
|
const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
|
|
7312
|
-
const targetPath2 =
|
|
7377
|
+
const targetPath2 = path36.join(pendingSyncDir, filename2);
|
|
7313
7378
|
createdItems.push({
|
|
7314
7379
|
proposalId: proposalId2,
|
|
7315
7380
|
remoteId: item.remote_id,
|
|
@@ -7322,7 +7387,7 @@ async function runIntakeBranch(opts) {
|
|
|
7322
7387
|
const num = proposalId.replace("PROP-", "");
|
|
7323
7388
|
const slug = slugify(item.title ?? "untitled");
|
|
7324
7389
|
const filename = `PROPOSAL-${num}-remote-${slug}.md`;
|
|
7325
|
-
const targetPath =
|
|
7390
|
+
const targetPath = path36.join(pendingSyncDir, filename);
|
|
7326
7391
|
const nowTs = now();
|
|
7327
7392
|
const fm = {
|
|
7328
7393
|
proposal_id: proposalId,
|
|
@@ -7414,8 +7479,8 @@ path/to/new/file.ext - {Explanation of purpose}
|
|
|
7414
7479
|
}
|
|
7415
7480
|
async function hasAnyRemoteAuthored(projectRoot) {
|
|
7416
7481
|
const dirs = [
|
|
7417
|
-
|
|
7418
|
-
|
|
7482
|
+
path36.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
7483
|
+
path36.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
7419
7484
|
];
|
|
7420
7485
|
for (const dir of dirs) {
|
|
7421
7486
|
let entries;
|
|
@@ -7426,7 +7491,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
7426
7491
|
}
|
|
7427
7492
|
for (const entry of entries) {
|
|
7428
7493
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7429
|
-
const fullPath =
|
|
7494
|
+
const fullPath = path36.join(dir, entry.name);
|
|
7430
7495
|
try {
|
|
7431
7496
|
const raw = await fsPromises4.readFile(fullPath, "utf8");
|
|
7432
7497
|
const fmEnd = raw.indexOf("\n---", 4);
|
|
@@ -7443,9 +7508,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
7443
7508
|
}
|
|
7444
7509
|
|
|
7445
7510
|
// src/lib/active-criteria.ts
|
|
7446
|
-
import * as
|
|
7511
|
+
import * as fs33 from "fs";
|
|
7447
7512
|
import * as fsPromises5 from "fs/promises";
|
|
7448
|
-
import * as
|
|
7513
|
+
import * as path37 from "path";
|
|
7449
7514
|
async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
7450
7515
|
const active = /* @__PURE__ */ new Set();
|
|
7451
7516
|
const now = Date.parse(nowFn());
|
|
@@ -7470,7 +7535,7 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
7470
7535
|
const ids = /* @__PURE__ */ new Set();
|
|
7471
7536
|
try {
|
|
7472
7537
|
const sprintDir = resolveActiveSprintDir(projectRoot);
|
|
7473
|
-
const sprintId =
|
|
7538
|
+
const sprintId = path37.basename(sprintDir);
|
|
7474
7539
|
if (sprintId === "_off-sprint") return ids;
|
|
7475
7540
|
const sprintFile = await findSprintFile(projectRoot, sprintId);
|
|
7476
7541
|
if (!sprintFile) return ids;
|
|
@@ -7485,14 +7550,14 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
7485
7550
|
return ids;
|
|
7486
7551
|
}
|
|
7487
7552
|
async function findSprintFile(projectRoot, sprintId) {
|
|
7488
|
-
const pendingSync =
|
|
7489
|
-
const archive =
|
|
7553
|
+
const pendingSync = path37.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
7554
|
+
const archive = path37.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
7490
7555
|
for (const dir of [pendingSync, archive]) {
|
|
7491
7556
|
try {
|
|
7492
|
-
const entries =
|
|
7557
|
+
const entries = fs33.readdirSync(dir, { withFileTypes: true });
|
|
7493
7558
|
for (const entry of entries) {
|
|
7494
7559
|
if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
|
|
7495
|
-
return
|
|
7560
|
+
return path37.join(dir, entry.name);
|
|
7496
7561
|
}
|
|
7497
7562
|
}
|
|
7498
7563
|
} catch {
|
|
@@ -7503,12 +7568,12 @@ async function findSprintFile(projectRoot, sprintId) {
|
|
|
7503
7568
|
|
|
7504
7569
|
// src/lib/comments-cache.ts
|
|
7505
7570
|
import * as fsPromises6 from "fs/promises";
|
|
7506
|
-
import * as
|
|
7571
|
+
import * as path38 from "path";
|
|
7507
7572
|
function cacheDir(projectRoot) {
|
|
7508
|
-
return
|
|
7573
|
+
return path38.join(projectRoot, ".cleargate", ".comments-cache");
|
|
7509
7574
|
}
|
|
7510
7575
|
function cachePath(projectRoot, remoteId) {
|
|
7511
|
-
return
|
|
7576
|
+
return path38.join(cacheDir(projectRoot), `${remoteId}.json`);
|
|
7512
7577
|
}
|
|
7513
7578
|
async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
7514
7579
|
const dir = cacheDir(projectRoot);
|
|
@@ -7522,7 +7587,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
|
7522
7587
|
|
|
7523
7588
|
// src/lib/wiki-comments-render.ts
|
|
7524
7589
|
import * as fsPromises7 from "fs/promises";
|
|
7525
|
-
import * as
|
|
7590
|
+
import * as path39 from "path";
|
|
7526
7591
|
var START = "<!-- cleargate:comments:start -->";
|
|
7527
7592
|
var END = "<!-- cleargate:comments:end -->";
|
|
7528
7593
|
function resolveBucket(fm) {
|
|
@@ -7567,7 +7632,7 @@ async function renderCommentsSection(opts) {
|
|
|
7567
7632
|
const bucket = resolveBucket(localItem.fm);
|
|
7568
7633
|
const primaryId = getPrimaryId(localItem.fm);
|
|
7569
7634
|
if (!bucket || !primaryId) return;
|
|
7570
|
-
const wikiPath =
|
|
7635
|
+
const wikiPath = path39.join(
|
|
7571
7636
|
projectRoot,
|
|
7572
7637
|
".cleargate",
|
|
7573
7638
|
"wiki",
|
|
@@ -7603,7 +7668,7 @@ async function renderCommentsSection(opts) {
|
|
|
7603
7668
|
await writeAtomic4(wikiPath, updated);
|
|
7604
7669
|
}
|
|
7605
7670
|
async function writeAtomic4(filePath, content) {
|
|
7606
|
-
await fsPromises7.mkdir(
|
|
7671
|
+
await fsPromises7.mkdir(path39.dirname(filePath), { recursive: true });
|
|
7607
7672
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
7608
7673
|
await fsPromises7.writeFile(tmpPath, content, "utf8");
|
|
7609
7674
|
await fsPromises7.rename(tmpPath, filePath);
|
|
@@ -7615,11 +7680,11 @@ async function syncCheckHandler(opts = {}) {
|
|
|
7615
7680
|
const env = opts.env ?? process.env;
|
|
7616
7681
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
7617
7682
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
7618
|
-
const markerPath =
|
|
7683
|
+
const markerPath = path40.join(projectRoot, ".cleargate", ".sync-marker.json");
|
|
7619
7684
|
const updateMarker = async (nowIso2) => {
|
|
7620
7685
|
try {
|
|
7621
7686
|
const content = JSON.stringify({ last_check: nowIso2 });
|
|
7622
|
-
await fsPromises8.mkdir(
|
|
7687
|
+
await fsPromises8.mkdir(path40.dirname(markerPath), { recursive: true });
|
|
7623
7688
|
const tmpPath = `${markerPath}.tmp.${Date.now()}`;
|
|
7624
7689
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
7625
7690
|
await fsPromises8.rename(tmpPath, markerPath);
|
|
@@ -7702,7 +7767,7 @@ async function syncHandler(opts = {}) {
|
|
|
7702
7767
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
7703
7768
|
const identity = resolveIdentity(projectRoot);
|
|
7704
7769
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
7705
|
-
const sprintId =
|
|
7770
|
+
const sprintId = path40.basename(sprintRoot);
|
|
7706
7771
|
let mcp;
|
|
7707
7772
|
if (opts.mcp) {
|
|
7708
7773
|
mcp = opts.mcp;
|
|
@@ -7763,7 +7828,7 @@ async function syncHandler(opts = {}) {
|
|
|
7763
7828
|
exit(2);
|
|
7764
7829
|
return;
|
|
7765
7830
|
}
|
|
7766
|
-
const wikiMetaPath =
|
|
7831
|
+
const wikiMetaPath = path40.join(projectRoot, ".cleargate", "wiki", "meta.json");
|
|
7767
7832
|
let lastRemoteSync = "1970-01-01T00:00:00.000Z";
|
|
7768
7833
|
try {
|
|
7769
7834
|
const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -8004,7 +8069,7 @@ async function syncHandler(opts = {}) {
|
|
|
8004
8069
|
};
|
|
8005
8070
|
await appendSyncLog(sprintRoot, entry);
|
|
8006
8071
|
}
|
|
8007
|
-
const conflictsFile =
|
|
8072
|
+
const conflictsFile = path40.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
8008
8073
|
const conflictsContent = {
|
|
8009
8074
|
generated_at: nowFn(),
|
|
8010
8075
|
sprint_id: sprintId,
|
|
@@ -8012,7 +8077,7 @@ async function syncHandler(opts = {}) {
|
|
|
8012
8077
|
};
|
|
8013
8078
|
await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
|
|
8014
8079
|
try {
|
|
8015
|
-
await fsPromises8.mkdir(
|
|
8080
|
+
await fsPromises8.mkdir(path40.dirname(wikiMetaPath), { recursive: true });
|
|
8016
8081
|
let meta = {};
|
|
8017
8082
|
try {
|
|
8018
8083
|
const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -8053,13 +8118,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
|
|
|
8053
8118
|
await writeAtomic5(localPath, newContent);
|
|
8054
8119
|
}
|
|
8055
8120
|
async function writeAtomic5(filePath, content) {
|
|
8056
|
-
await fsPromises8.mkdir(
|
|
8121
|
+
await fsPromises8.mkdir(path40.dirname(filePath), { recursive: true });
|
|
8057
8122
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
8058
8123
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
8059
8124
|
await fsPromises8.rename(tmpPath, filePath);
|
|
8060
8125
|
}
|
|
8061
8126
|
async function scanLocalItems(projectRoot) {
|
|
8062
|
-
const pendingSync =
|
|
8127
|
+
const pendingSync = path40.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8063
8128
|
const results = [];
|
|
8064
8129
|
let entries;
|
|
8065
8130
|
try {
|
|
@@ -8069,7 +8134,7 @@ async function scanLocalItems(projectRoot) {
|
|
|
8069
8134
|
}
|
|
8070
8135
|
for (const entry of entries) {
|
|
8071
8136
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8072
|
-
const fullPath =
|
|
8137
|
+
const fullPath = path40.join(pendingSync, entry.name);
|
|
8073
8138
|
try {
|
|
8074
8139
|
const raw = await fsPromises8.readFile(fullPath, "utf8");
|
|
8075
8140
|
const { fm, body } = parseFrontmatter(raw);
|
|
@@ -8091,7 +8156,7 @@ function getItemId(fm) {
|
|
|
8091
8156
|
|
|
8092
8157
|
// src/commands/pull.ts
|
|
8093
8158
|
import * as fsPromises9 from "fs/promises";
|
|
8094
|
-
import * as
|
|
8159
|
+
import * as path41 from "path";
|
|
8095
8160
|
async function pullHandler(idOrRemoteId, opts = {}) {
|
|
8096
8161
|
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
8097
8162
|
const env = opts.env ?? process.env;
|
|
@@ -8204,7 +8269,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
8204
8269
|
result: "ok"
|
|
8205
8270
|
};
|
|
8206
8271
|
await appendSyncLog(sprintRoot, entry);
|
|
8207
|
-
stdout(`pull: ${remoteId} applied to ${
|
|
8272
|
+
stdout(`pull: ${remoteId} applied to ${path41.relative(projectRoot, localPath)}
|
|
8208
8273
|
`);
|
|
8209
8274
|
if (opts.comments) {
|
|
8210
8275
|
const comments = await mcp.call(
|
|
@@ -8227,7 +8292,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
8227
8292
|
if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
|
|
8228
8293
|
return idOrRemoteId;
|
|
8229
8294
|
}
|
|
8230
|
-
const pendingSync =
|
|
8295
|
+
const pendingSync = path41.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8231
8296
|
let entries;
|
|
8232
8297
|
try {
|
|
8233
8298
|
entries = await fsPromises9.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -8237,7 +8302,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
8237
8302
|
for (const entry of entries) {
|
|
8238
8303
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8239
8304
|
try {
|
|
8240
|
-
const raw = await fsPromises9.readFile(
|
|
8305
|
+
const raw = await fsPromises9.readFile(path41.join(pendingSync, entry.name), "utf8");
|
|
8241
8306
|
const { fm } = parseFrontmatter(raw);
|
|
8242
8307
|
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
8243
8308
|
if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
|
|
@@ -8250,7 +8315,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
8250
8315
|
return null;
|
|
8251
8316
|
}
|
|
8252
8317
|
async function findLocalFile(remoteId, projectRoot) {
|
|
8253
|
-
const pendingSync =
|
|
8318
|
+
const pendingSync = path41.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8254
8319
|
let entries;
|
|
8255
8320
|
try {
|
|
8256
8321
|
entries = await fsPromises9.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -8259,7 +8324,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
8259
8324
|
}
|
|
8260
8325
|
for (const entry of entries) {
|
|
8261
8326
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8262
|
-
const fullPath =
|
|
8327
|
+
const fullPath = path41.join(pendingSync, entry.name);
|
|
8263
8328
|
try {
|
|
8264
8329
|
const raw = await fsPromises9.readFile(fullPath, "utf8");
|
|
8265
8330
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -8270,7 +8335,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
8270
8335
|
return null;
|
|
8271
8336
|
}
|
|
8272
8337
|
async function writeAtomic6(filePath, content) {
|
|
8273
|
-
await fsPromises9.mkdir(
|
|
8338
|
+
await fsPromises9.mkdir(path41.dirname(filePath), { recursive: true });
|
|
8274
8339
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
8275
8340
|
await fsPromises9.writeFile(tmpPath, content, "utf8");
|
|
8276
8341
|
await fsPromises9.rename(tmpPath, filePath);
|
|
@@ -8285,7 +8350,7 @@ function getItemId2(fm) {
|
|
|
8285
8350
|
|
|
8286
8351
|
// src/commands/push.ts
|
|
8287
8352
|
import * as fsPromises10 from "fs/promises";
|
|
8288
|
-
import * as
|
|
8353
|
+
import * as path42 from "path";
|
|
8289
8354
|
async function pushHandler(fileOrId, opts = {}) {
|
|
8290
8355
|
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
8291
8356
|
const env = opts.env ?? process.env;
|
|
@@ -8359,7 +8424,7 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
8359
8424
|
}
|
|
8360
8425
|
async function handlePush(filePath, ctx) {
|
|
8361
8426
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
8362
|
-
const resolvedPath =
|
|
8427
|
+
const resolvedPath = path42.isAbsolute(filePath) ? filePath : path42.resolve(projectRoot, filePath);
|
|
8363
8428
|
let rawContent;
|
|
8364
8429
|
try {
|
|
8365
8430
|
rawContent = await fsPromises10.readFile(resolvedPath, "utf8");
|
|
@@ -8485,8 +8550,8 @@ async function handleRevert(idOrRemoteId, ctx) {
|
|
|
8485
8550
|
void localPath;
|
|
8486
8551
|
}
|
|
8487
8552
|
async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
8488
|
-
const pendingSync =
|
|
8489
|
-
const archive =
|
|
8553
|
+
const pendingSync = path42.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8554
|
+
const archive = path42.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
8490
8555
|
for (const dir of [pendingSync, archive]) {
|
|
8491
8556
|
let entries;
|
|
8492
8557
|
try {
|
|
@@ -8496,7 +8561,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
8496
8561
|
}
|
|
8497
8562
|
for (const entry of entries) {
|
|
8498
8563
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8499
|
-
const fullPath =
|
|
8564
|
+
const fullPath = path42.join(dir, entry.name);
|
|
8500
8565
|
try {
|
|
8501
8566
|
const raw = await fsPromises10.readFile(fullPath, "utf8");
|
|
8502
8567
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -8515,7 +8580,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
8515
8580
|
return null;
|
|
8516
8581
|
}
|
|
8517
8582
|
async function writeAtomic7(filePath, content) {
|
|
8518
|
-
await fsPromises10.mkdir(
|
|
8583
|
+
await fsPromises10.mkdir(path42.dirname(filePath), { recursive: true });
|
|
8519
8584
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
8520
8585
|
await fsPromises10.writeFile(tmpPath, content, "utf8");
|
|
8521
8586
|
await fsPromises10.rename(tmpPath, filePath);
|
|
@@ -8543,7 +8608,7 @@ function getItemType(fm) {
|
|
|
8543
8608
|
|
|
8544
8609
|
// src/commands/conflicts.ts
|
|
8545
8610
|
import * as fsPromises11 from "fs/promises";
|
|
8546
|
-
import * as
|
|
8611
|
+
import * as path43 from "path";
|
|
8547
8612
|
var RESOLUTION_HINTS = {
|
|
8548
8613
|
"local-delete-remote-edit": "remote-delete: resurrect or delete remote?",
|
|
8549
8614
|
"remote-delete-local-edit": "local-edit: push your changes or accept remote deletion?",
|
|
@@ -8579,7 +8644,7 @@ async function conflictsHandler(opts = {}) {
|
|
|
8579
8644
|
}
|
|
8580
8645
|
}
|
|
8581
8646
|
}
|
|
8582
|
-
const conflictsFile =
|
|
8647
|
+
const conflictsFile = path43.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
8583
8648
|
let data;
|
|
8584
8649
|
try {
|
|
8585
8650
|
const raw = await fsPromises11.readFile(conflictsFile, "utf8");
|
|
@@ -8665,8 +8730,8 @@ function formatEntry(entry) {
|
|
|
8665
8730
|
}
|
|
8666
8731
|
|
|
8667
8732
|
// src/commands/admin-login.ts
|
|
8668
|
-
import * as
|
|
8669
|
-
import * as
|
|
8733
|
+
import * as fs34 from "fs";
|
|
8734
|
+
import * as path44 from "path";
|
|
8670
8735
|
import * as os5 from "os";
|
|
8671
8736
|
var DEFAULT_MCP_URL = "http://localhost:3000";
|
|
8672
8737
|
function resolveMcpUrl(mcpUrlFlag, env) {
|
|
@@ -8675,14 +8740,14 @@ function resolveMcpUrl(mcpUrlFlag, env) {
|
|
|
8675
8740
|
function resolveAuthFilePath(opts) {
|
|
8676
8741
|
if (opts.authFilePath) return opts.authFilePath;
|
|
8677
8742
|
const homedirFn = opts.homedir ?? os5.homedir;
|
|
8678
|
-
return
|
|
8743
|
+
return path44.join(homedirFn(), ".cleargate", "admin-auth.json");
|
|
8679
8744
|
}
|
|
8680
8745
|
function writeAdminAuth(filePath, token) {
|
|
8681
|
-
const dir =
|
|
8682
|
-
|
|
8746
|
+
const dir = path44.dirname(filePath);
|
|
8747
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
8683
8748
|
const payload = JSON.stringify({ version: 1, token }, null, 2);
|
|
8684
|
-
|
|
8685
|
-
|
|
8749
|
+
fs34.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
|
|
8750
|
+
fs34.chmodSync(filePath, 384);
|
|
8686
8751
|
}
|
|
8687
8752
|
async function adminLoginHandler(opts = {}) {
|
|
8688
8753
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
@@ -8791,8 +8856,8 @@ async function adminLoginHandler(opts = {}) {
|
|
|
8791
8856
|
}
|
|
8792
8857
|
|
|
8793
8858
|
// src/commands/hotfix.ts
|
|
8794
|
-
import * as
|
|
8795
|
-
import * as
|
|
8859
|
+
import * as fs35 from "fs";
|
|
8860
|
+
import * as path45 from "path";
|
|
8796
8861
|
function defaultExit4(code) {
|
|
8797
8862
|
return process.exit(code);
|
|
8798
8863
|
}
|
|
@@ -8802,7 +8867,7 @@ function maxHotfixId(pendingDir) {
|
|
|
8802
8867
|
let max = 0;
|
|
8803
8868
|
let entries;
|
|
8804
8869
|
try {
|
|
8805
|
-
entries =
|
|
8870
|
+
entries = fs35.readdirSync(pendingDir);
|
|
8806
8871
|
} catch {
|
|
8807
8872
|
return 0;
|
|
8808
8873
|
}
|
|
@@ -8816,13 +8881,13 @@ function maxHotfixId(pendingDir) {
|
|
|
8816
8881
|
return max;
|
|
8817
8882
|
}
|
|
8818
8883
|
function countActiveHotfixes(repoRoot) {
|
|
8819
|
-
const pendingDir =
|
|
8820
|
-
const archiveDir =
|
|
8884
|
+
const pendingDir = path45.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
8885
|
+
const archiveDir = path45.join(repoRoot, ".cleargate", "delivery", "archive");
|
|
8821
8886
|
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
8822
8887
|
let count = 0;
|
|
8823
8888
|
let pendingEntries = [];
|
|
8824
8889
|
try {
|
|
8825
|
-
pendingEntries =
|
|
8890
|
+
pendingEntries = fs35.readdirSync(pendingDir);
|
|
8826
8891
|
} catch {
|
|
8827
8892
|
}
|
|
8828
8893
|
for (const entry of pendingEntries) {
|
|
@@ -8830,13 +8895,13 @@ function countActiveHotfixes(repoRoot) {
|
|
|
8830
8895
|
}
|
|
8831
8896
|
let archiveEntries = [];
|
|
8832
8897
|
try {
|
|
8833
|
-
archiveEntries =
|
|
8898
|
+
archiveEntries = fs35.readdirSync(archiveDir);
|
|
8834
8899
|
} catch {
|
|
8835
8900
|
}
|
|
8836
8901
|
for (const entry of archiveEntries) {
|
|
8837
8902
|
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
|
|
8838
8903
|
try {
|
|
8839
|
-
const stat =
|
|
8904
|
+
const stat = fs35.statSync(path45.join(archiveDir, entry));
|
|
8840
8905
|
if (stat.mtimeMs >= sevenDaysAgo) count++;
|
|
8841
8906
|
} catch {
|
|
8842
8907
|
}
|
|
@@ -8845,7 +8910,7 @@ function countActiveHotfixes(repoRoot) {
|
|
|
8845
8910
|
return count;
|
|
8846
8911
|
}
|
|
8847
8912
|
function resolveTemplatePath(repoRoot) {
|
|
8848
|
-
return
|
|
8913
|
+
return path45.join(repoRoot, ".cleargate", "templates", "hotfix.md");
|
|
8849
8914
|
}
|
|
8850
8915
|
function hotfixNewHandler(opts, cli) {
|
|
8851
8916
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -8864,14 +8929,14 @@ function hotfixNewHandler(opts, cli) {
|
|
|
8864
8929
|
);
|
|
8865
8930
|
return exitFn(1);
|
|
8866
8931
|
}
|
|
8867
|
-
const pendingDir =
|
|
8932
|
+
const pendingDir = path45.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
8868
8933
|
const maxId = maxHotfixId(pendingDir);
|
|
8869
8934
|
const nextId = maxId + 1;
|
|
8870
8935
|
const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
|
|
8871
8936
|
const templatePath = resolveTemplatePath(repoRoot);
|
|
8872
8937
|
let templateContent;
|
|
8873
8938
|
try {
|
|
8874
|
-
templateContent =
|
|
8939
|
+
templateContent = fs35.readFileSync(templatePath, "utf8");
|
|
8875
8940
|
} catch {
|
|
8876
8941
|
stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
|
|
8877
8942
|
return exitFn(2);
|
|
@@ -8879,10 +8944,10 @@ function hotfixNewHandler(opts, cli) {
|
|
|
8879
8944
|
const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
|
|
8880
8945
|
const fileSlug = opts.slug.replace(/-/g, "_");
|
|
8881
8946
|
const fileName = `${idStr}_${fileSlug}.md`;
|
|
8882
|
-
const outPath =
|
|
8947
|
+
const outPath = path45.join(pendingDir, fileName);
|
|
8883
8948
|
try {
|
|
8884
|
-
|
|
8885
|
-
|
|
8949
|
+
fs35.mkdirSync(pendingDir, { recursive: true });
|
|
8950
|
+
fs35.writeFileSync(outPath, content, "utf8");
|
|
8886
8951
|
} catch (err) {
|
|
8887
8952
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8888
8953
|
stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
|