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