cleargate 0.6.2 → 0.8.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 +773 -484
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +778 -494
- 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.cjs
CHANGED
|
@@ -627,7 +627,7 @@ var import_commander = require("commander");
|
|
|
627
627
|
// package.json
|
|
628
628
|
var package_default = {
|
|
629
629
|
name: "cleargate",
|
|
630
|
-
version: "0.
|
|
630
|
+
version: "0.8.0",
|
|
631
631
|
private: false,
|
|
632
632
|
type: "module",
|
|
633
633
|
description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
|
|
@@ -1897,8 +1897,8 @@ async function stampHandler(file, opts, cli) {
|
|
|
1897
1897
|
|
|
1898
1898
|
// src/commands/init.ts
|
|
1899
1899
|
init_cjs_shims();
|
|
1900
|
-
var
|
|
1901
|
-
var
|
|
1900
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
1901
|
+
var path17 = __toESM(require("path"), 1);
|
|
1902
1902
|
var import_node_url5 = require("url");
|
|
1903
1903
|
var import_node_child_process3 = require("child_process");
|
|
1904
1904
|
|
|
@@ -1945,9 +1945,14 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1945
1945
|
srcContent = text;
|
|
1946
1946
|
}
|
|
1947
1947
|
const srcBuffer = typeof srcContent === "string" ? Buffer.from(srcContent, "utf8") : srcContent;
|
|
1948
|
+
const srcMode = fs7.statSync(srcPath).mode;
|
|
1949
|
+
const needsExec = (srcMode & 73) !== 0 || relPath.endsWith(".sh");
|
|
1948
1950
|
if (fs7.existsSync(dstPath)) {
|
|
1949
1951
|
const dstContent = fs7.readFileSync(dstPath);
|
|
1950
1952
|
if (srcBuffer.equals(dstContent)) {
|
|
1953
|
+
if (needsExec && process.platform !== "win32") {
|
|
1954
|
+
fs7.chmodSync(dstPath, 493);
|
|
1955
|
+
}
|
|
1951
1956
|
report.skipped++;
|
|
1952
1957
|
report.actions.push({ action: "skipped", relPath });
|
|
1953
1958
|
continue;
|
|
@@ -1958,10 +1963,16 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1958
1963
|
continue;
|
|
1959
1964
|
}
|
|
1960
1965
|
fs7.writeFileSync(dstPath, srcBuffer);
|
|
1966
|
+
if (needsExec && process.platform !== "win32") {
|
|
1967
|
+
fs7.chmodSync(dstPath, 493);
|
|
1968
|
+
}
|
|
1961
1969
|
report.overwritten++;
|
|
1962
1970
|
report.actions.push({ action: "overwritten", relPath });
|
|
1963
1971
|
} else {
|
|
1964
1972
|
fs7.writeFileSync(dstPath, srcBuffer);
|
|
1973
|
+
if (needsExec && process.platform !== "win32") {
|
|
1974
|
+
fs7.chmodSync(dstPath, 493);
|
|
1975
|
+
}
|
|
1965
1976
|
report.created++;
|
|
1966
1977
|
report.actions.push({ action: "created", relPath });
|
|
1967
1978
|
}
|
|
@@ -2030,15 +2041,53 @@ function injectClaudeMd(existing, block) {
|
|
|
2030
2041
|
return existing.trimEnd() + "\n\n" + block + "\n";
|
|
2031
2042
|
}
|
|
2032
2043
|
|
|
2044
|
+
// src/init/inject-mcp-json.ts
|
|
2045
|
+
init_cjs_shims();
|
|
2046
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
2047
|
+
var path8 = __toESM(require("path"), 1);
|
|
2048
|
+
function mergeMcpJson(existing, entry) {
|
|
2049
|
+
const next = existing ? { ...existing } : {};
|
|
2050
|
+
const servers = { ...next.mcpServers ?? {} };
|
|
2051
|
+
servers.cleargate = entry;
|
|
2052
|
+
next.mcpServers = servers;
|
|
2053
|
+
return JSON.stringify(next, null, 2) + "\n";
|
|
2054
|
+
}
|
|
2055
|
+
var STDIO_ENTRY = {
|
|
2056
|
+
command: "cleargate",
|
|
2057
|
+
args: ["mcp", "serve"]
|
|
2058
|
+
};
|
|
2059
|
+
function injectMcpJson(cwd, _unusedUrl) {
|
|
2060
|
+
const dst = path8.join(cwd, ".mcp.json");
|
|
2061
|
+
const entry = STDIO_ENTRY;
|
|
2062
|
+
let existing = null;
|
|
2063
|
+
let existingRaw = null;
|
|
2064
|
+
if (fs8.existsSync(dst)) {
|
|
2065
|
+
existingRaw = fs8.readFileSync(dst, "utf8");
|
|
2066
|
+
try {
|
|
2067
|
+
existing = JSON.parse(existingRaw);
|
|
2068
|
+
} catch {
|
|
2069
|
+
throw new Error(
|
|
2070
|
+
`inject-mcp-json: ${dst} is not valid JSON; refusing to overwrite. Fix or remove the file and re-run init.`
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
const next = mergeMcpJson(existing, entry);
|
|
2075
|
+
if (existingRaw !== null && existingRaw === next) {
|
|
2076
|
+
return "unchanged";
|
|
2077
|
+
}
|
|
2078
|
+
fs8.writeFileSync(dst, next);
|
|
2079
|
+
return existingRaw === null ? "created" : "updated";
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2033
2082
|
// src/commands/wiki-build.ts
|
|
2034
2083
|
init_cjs_shims();
|
|
2035
|
-
var
|
|
2036
|
-
var
|
|
2084
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
2085
|
+
var path14 = __toESM(require("path"), 1);
|
|
2037
2086
|
|
|
2038
2087
|
// src/wiki/scan.ts
|
|
2039
2088
|
init_cjs_shims();
|
|
2040
|
-
var
|
|
2041
|
-
var
|
|
2089
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
2090
|
+
var path9 = __toESM(require("path"), 1);
|
|
2042
2091
|
|
|
2043
2092
|
// src/wiki/derive-bucket.ts
|
|
2044
2093
|
init_cjs_shims();
|
|
@@ -2083,19 +2132,19 @@ var EXCLUDED_SUFFIXES = [
|
|
|
2083
2132
|
function scanRawItems(deliveryRoot, repoRoot) {
|
|
2084
2133
|
const results = [];
|
|
2085
2134
|
for (const subdir of ["pending-sync", "archive"]) {
|
|
2086
|
-
const dir =
|
|
2087
|
-
if (!
|
|
2088
|
-
const entries =
|
|
2135
|
+
const dir = path9.join(deliveryRoot, subdir);
|
|
2136
|
+
if (!fs9.existsSync(dir)) continue;
|
|
2137
|
+
const entries = fs9.readdirSync(dir, { recursive: true, encoding: "utf8" });
|
|
2089
2138
|
for (const rel of entries) {
|
|
2090
2139
|
if (!rel.endsWith(".md")) continue;
|
|
2091
2140
|
if (rel.includes("~") || rel.startsWith(".")) continue;
|
|
2092
|
-
const absPath =
|
|
2093
|
-
const stat =
|
|
2141
|
+
const absPath = path9.join(dir, rel);
|
|
2142
|
+
const stat = fs9.statSync(absPath);
|
|
2094
2143
|
if (!stat.isFile()) continue;
|
|
2095
|
-
const rawPath =
|
|
2144
|
+
const rawPath = path9.relative(repoRoot, absPath).replace(/\\/g, "/");
|
|
2096
2145
|
const isExcluded = EXCLUDED_SUFFIXES.some((excl) => rawPath.startsWith(excl));
|
|
2097
2146
|
if (isExcluded) continue;
|
|
2098
|
-
const filename =
|
|
2147
|
+
const filename = path9.basename(absPath);
|
|
2099
2148
|
let bucketInfo;
|
|
2100
2149
|
try {
|
|
2101
2150
|
bucketInfo = deriveBucket(filename);
|
|
@@ -2108,7 +2157,7 @@ function scanRawItems(deliveryRoot, repoRoot) {
|
|
|
2108
2157
|
} catch {
|
|
2109
2158
|
continue;
|
|
2110
2159
|
}
|
|
2111
|
-
const raw =
|
|
2160
|
+
const raw = fs9.readFileSync(absPath, "utf8");
|
|
2112
2161
|
let fm;
|
|
2113
2162
|
let body;
|
|
2114
2163
|
try {
|
|
@@ -2223,8 +2272,8 @@ function parseFmRaw(raw) {
|
|
|
2223
2272
|
|
|
2224
2273
|
// src/wiki/synthesis/active-sprint.ts
|
|
2225
2274
|
init_cjs_shims();
|
|
2226
|
-
var
|
|
2227
|
-
var
|
|
2275
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
2276
|
+
var path10 = __toESM(require("path"), 1);
|
|
2228
2277
|
var import_node_url = require("url");
|
|
2229
2278
|
|
|
2230
2279
|
// src/wiki/synthesis/render.ts
|
|
@@ -2264,7 +2313,7 @@ function renderSection(template, ctx) {
|
|
|
2264
2313
|
// src/wiki/synthesis/active-sprint.ts
|
|
2265
2314
|
function compile(state2, templateDir) {
|
|
2266
2315
|
const tplDir = templateDir ?? resolveDefaultTemplateDir();
|
|
2267
|
-
const tpl =
|
|
2316
|
+
const tpl = fs10.readFileSync(path10.join(tplDir, "active-sprint.md"), "utf8");
|
|
2268
2317
|
const sprints = state2.filter((i) => i.bucket === "sprints");
|
|
2269
2318
|
const active = sprints.filter((s) => isSet(s.fm["activated_at"]) && !isSet(s.fm["completed_at"]));
|
|
2270
2319
|
const completed = sprints.filter((s) => isSet(s.fm["completed_at"]));
|
|
@@ -2288,18 +2337,18 @@ function isSet(val) {
|
|
|
2288
2337
|
return s !== "" && s !== "null";
|
|
2289
2338
|
}
|
|
2290
2339
|
function resolveDefaultTemplateDir() {
|
|
2291
|
-
const __dirname =
|
|
2292
|
-
return
|
|
2340
|
+
const __dirname = path10.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
2341
|
+
return path10.resolve(__dirname, "..", "templates", "synthesis");
|
|
2293
2342
|
}
|
|
2294
2343
|
|
|
2295
2344
|
// src/wiki/synthesis/open-gates.ts
|
|
2296
2345
|
init_cjs_shims();
|
|
2297
|
-
var
|
|
2298
|
-
var
|
|
2346
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
2347
|
+
var path11 = __toESM(require("path"), 1);
|
|
2299
2348
|
var import_node_url2 = require("url");
|
|
2300
2349
|
function compile2(state2, templateDir) {
|
|
2301
2350
|
const tplDir = templateDir ?? resolveDefaultTemplateDir2();
|
|
2302
|
-
const tpl =
|
|
2351
|
+
const tpl = fs11.readFileSync(path11.join(tplDir, "open-gates.md"), "utf8");
|
|
2303
2352
|
const gate1 = state2.filter((i) => {
|
|
2304
2353
|
if (i.bucket !== "proposals") return false;
|
|
2305
2354
|
const status = String(i.fm["status"] ?? "");
|
|
@@ -2328,18 +2377,18 @@ function compile2(state2, templateDir) {
|
|
|
2328
2377
|
return renderTemplate(tpl, data);
|
|
2329
2378
|
}
|
|
2330
2379
|
function resolveDefaultTemplateDir2() {
|
|
2331
|
-
const __dirname =
|
|
2332
|
-
return
|
|
2380
|
+
const __dirname = path11.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
2381
|
+
return path11.resolve(__dirname, "..", "templates", "synthesis");
|
|
2333
2382
|
}
|
|
2334
2383
|
|
|
2335
2384
|
// src/wiki/synthesis/product-state.ts
|
|
2336
2385
|
init_cjs_shims();
|
|
2337
|
-
var
|
|
2338
|
-
var
|
|
2386
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
2387
|
+
var path12 = __toESM(require("path"), 1);
|
|
2339
2388
|
var import_node_url3 = require("url");
|
|
2340
2389
|
function compile3(state2, templateDir) {
|
|
2341
2390
|
const tplDir = templateDir ?? resolveDefaultTemplateDir3();
|
|
2342
|
-
const tpl =
|
|
2391
|
+
const tpl = fs12.readFileSync(path12.join(tplDir, "product-state.md"), "utf8");
|
|
2343
2392
|
function countBucket(bucket) {
|
|
2344
2393
|
return state2.filter((i) => i.bucket === bucket);
|
|
2345
2394
|
}
|
|
@@ -2386,18 +2435,18 @@ function compile3(state2, templateDir) {
|
|
|
2386
2435
|
return renderTemplate(tpl, data);
|
|
2387
2436
|
}
|
|
2388
2437
|
function resolveDefaultTemplateDir3() {
|
|
2389
|
-
const __dirname =
|
|
2390
|
-
return
|
|
2438
|
+
const __dirname = path12.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
2439
|
+
return path12.resolve(__dirname, "..", "templates", "synthesis");
|
|
2391
2440
|
}
|
|
2392
2441
|
|
|
2393
2442
|
// src/wiki/synthesis/roadmap.ts
|
|
2394
2443
|
init_cjs_shims();
|
|
2395
|
-
var
|
|
2396
|
-
var
|
|
2444
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
2445
|
+
var path13 = __toESM(require("path"), 1);
|
|
2397
2446
|
var import_node_url4 = require("url");
|
|
2398
2447
|
function compile4(state2, templateDir) {
|
|
2399
2448
|
const tplDir = templateDir ?? resolveDefaultTemplateDir4();
|
|
2400
|
-
const tpl =
|
|
2449
|
+
const tpl = fs13.readFileSync(path13.join(tplDir, "roadmap.md"), "utf8");
|
|
2401
2450
|
const sprints = state2.filter((i) => i.bucket === "sprints");
|
|
2402
2451
|
const epics = state2.filter((i) => i.bucket === "epics");
|
|
2403
2452
|
const inFlightSprints = sprints.filter(
|
|
@@ -2450,8 +2499,8 @@ function isShippedStatus(status) {
|
|
|
2450
2499
|
return status === "Completed" || status === "Approved";
|
|
2451
2500
|
}
|
|
2452
2501
|
function resolveDefaultTemplateDir4() {
|
|
2453
|
-
const __dirname =
|
|
2454
|
-
return
|
|
2502
|
+
const __dirname = path13.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
|
|
2503
|
+
return path13.resolve(__dirname, "..", "templates", "synthesis");
|
|
2455
2504
|
}
|
|
2456
2505
|
|
|
2457
2506
|
// src/commands/wiki-build.ts
|
|
@@ -2476,16 +2525,16 @@ async function wikiBuildHandler(opts = {}) {
|
|
|
2476
2525
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
2477
2526
|
const gitRunner = opts.gitRunner;
|
|
2478
2527
|
const templateDir = opts.templateDir;
|
|
2479
|
-
const deliveryRoot =
|
|
2480
|
-
const wikiRoot =
|
|
2481
|
-
if (!
|
|
2528
|
+
const deliveryRoot = path14.join(cwd, ".cleargate", "delivery");
|
|
2529
|
+
const wikiRoot = path14.join(cwd, ".cleargate", "wiki");
|
|
2530
|
+
if (!fs14.existsSync(deliveryRoot)) {
|
|
2482
2531
|
stderr(`wiki build: .cleargate/delivery/ not found at ${deliveryRoot}
|
|
2483
2532
|
`);
|
|
2484
2533
|
exit(1);
|
|
2485
2534
|
return;
|
|
2486
2535
|
}
|
|
2487
2536
|
for (const bucket of BUCKET_ORDER) {
|
|
2488
|
-
|
|
2537
|
+
fs14.mkdirSync(path14.join(wikiRoot, bucket), { recursive: true });
|
|
2489
2538
|
}
|
|
2490
2539
|
const items = scanRawItems(deliveryRoot, cwd);
|
|
2491
2540
|
const timestamp = now();
|
|
@@ -2508,19 +2557,19 @@ async function wikiBuildHandler(opts = {}) {
|
|
|
2508
2557
|
};
|
|
2509
2558
|
const body = buildPageBody(item, wikiPage);
|
|
2510
2559
|
const content = serializePage(wikiPage, body);
|
|
2511
|
-
const pageDir =
|
|
2512
|
-
|
|
2513
|
-
|
|
2560
|
+
const pageDir = path14.join(wikiRoot, item.bucket);
|
|
2561
|
+
fs14.mkdirSync(pageDir, { recursive: true });
|
|
2562
|
+
fs14.writeFileSync(path14.join(pageDir, `${item.id}.md`), content, "utf8");
|
|
2514
2563
|
pagesWritten++;
|
|
2515
2564
|
}
|
|
2516
2565
|
const indexContent = buildIndex(items);
|
|
2517
|
-
|
|
2566
|
+
fs14.writeFileSync(path14.join(wikiRoot, "index.md"), indexContent, "utf8");
|
|
2518
2567
|
const logContent = buildLog(items, timestamp);
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2568
|
+
fs14.writeFileSync(path14.join(wikiRoot, "log.md"), logContent, "utf8");
|
|
2569
|
+
fs14.writeFileSync(path14.join(wikiRoot, "active-sprint.md"), compile(items, templateDir), "utf8");
|
|
2570
|
+
fs14.writeFileSync(path14.join(wikiRoot, "open-gates.md"), compile2(items, templateDir), "utf8");
|
|
2571
|
+
fs14.writeFileSync(path14.join(wikiRoot, "product-state.md"), compile3(items, templateDir), "utf8");
|
|
2572
|
+
fs14.writeFileSync(path14.join(wikiRoot, "roadmap.md"), compile4(items, templateDir), "utf8");
|
|
2524
2573
|
stdout(`wiki build: OK (${pagesWritten} pages written)
|
|
2525
2574
|
`);
|
|
2526
2575
|
}
|
|
@@ -2687,7 +2736,7 @@ function buildLog(items, timestamp) {
|
|
|
2687
2736
|
init_cjs_shims();
|
|
2688
2737
|
var import_promises2 = require("fs/promises");
|
|
2689
2738
|
var import_node_fs = require("fs");
|
|
2690
|
-
var
|
|
2739
|
+
var path15 = __toESM(require("path"), 1);
|
|
2691
2740
|
|
|
2692
2741
|
// src/lib/sha256.ts
|
|
2693
2742
|
init_cjs_shims();
|
|
@@ -2711,23 +2760,23 @@ function shortHash(full) {
|
|
|
2711
2760
|
// src/lib/manifest.ts
|
|
2712
2761
|
function resolveDefaultPackageRoot() {
|
|
2713
2762
|
const here = new URL(".", importMetaUrl).pathname;
|
|
2714
|
-
const distCandidate =
|
|
2763
|
+
const distCandidate = path15.join(here, "MANIFEST.json");
|
|
2715
2764
|
if ((0, import_node_fs.existsSync)(distCandidate)) {
|
|
2716
2765
|
return here;
|
|
2717
2766
|
}
|
|
2718
|
-
const oneLevelUp =
|
|
2767
|
+
const oneLevelUp = path15.join(here, "..", "MANIFEST.json");
|
|
2719
2768
|
if ((0, import_node_fs.existsSync)(oneLevelUp)) {
|
|
2720
|
-
return
|
|
2769
|
+
return path15.join(here, "..");
|
|
2721
2770
|
}
|
|
2722
|
-
const devCandidate =
|
|
2771
|
+
const devCandidate = path15.join(here, "..", "..", "..", "cleargate-planning", "MANIFEST.json");
|
|
2723
2772
|
if ((0, import_node_fs.existsSync)(devCandidate)) {
|
|
2724
|
-
return
|
|
2773
|
+
return path15.join(here, "..", "..", "..", "cleargate-planning");
|
|
2725
2774
|
}
|
|
2726
2775
|
return here;
|
|
2727
2776
|
}
|
|
2728
2777
|
function loadPackageManifest(opts) {
|
|
2729
2778
|
const packageRoot = opts?.packageRoot ?? resolveDefaultPackageRoot();
|
|
2730
|
-
const manifestPath =
|
|
2779
|
+
const manifestPath = path15.join(packageRoot, "MANIFEST.json");
|
|
2731
2780
|
if (!(0, import_node_fs.existsSync)(manifestPath)) {
|
|
2732
2781
|
throw new Error(
|
|
2733
2782
|
`MANIFEST.json not found at ${manifestPath}; run 'npm run build' to generate it.`
|
|
@@ -2744,7 +2793,7 @@ function loadPackageManifest(opts) {
|
|
|
2744
2793
|
return JSON.parse(raw);
|
|
2745
2794
|
}
|
|
2746
2795
|
async function loadInstallSnapshot(projectRoot) {
|
|
2747
|
-
const snapshotPath =
|
|
2796
|
+
const snapshotPath = path15.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
2748
2797
|
try {
|
|
2749
2798
|
const raw = await (0, import_promises2.readFile)(snapshotPath, "utf-8");
|
|
2750
2799
|
return JSON.parse(raw);
|
|
@@ -2753,7 +2802,7 @@ async function loadInstallSnapshot(projectRoot) {
|
|
|
2753
2802
|
}
|
|
2754
2803
|
}
|
|
2755
2804
|
async function computeCurrentSha(file, projectRoot) {
|
|
2756
|
-
const filePath =
|
|
2805
|
+
const filePath = path15.join(projectRoot, file.path);
|
|
2757
2806
|
try {
|
|
2758
2807
|
const raw = await (0, import_promises2.readFile)(filePath);
|
|
2759
2808
|
return hashNormalized(raw);
|
|
@@ -2782,8 +2831,8 @@ function classify(pkgSha, installSha, currentSha, tier) {
|
|
|
2782
2831
|
return "both-changed";
|
|
2783
2832
|
}
|
|
2784
2833
|
async function writeDriftState(projectRoot, state2, opts) {
|
|
2785
|
-
const cleargatDir =
|
|
2786
|
-
const finalPath =
|
|
2834
|
+
const cleargatDir = path15.join(projectRoot, ".cleargate");
|
|
2835
|
+
const finalPath = path15.join(cleargatDir, ".drift-state.json");
|
|
2787
2836
|
const tmpPath = `${finalPath}.tmp`;
|
|
2788
2837
|
const lastRefreshed = opts?.lastRefreshed ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2789
2838
|
const fileContent = { last_refreshed: lastRefreshed, drift: state2 };
|
|
@@ -2792,7 +2841,7 @@ async function writeDriftState(projectRoot, state2, opts) {
|
|
|
2792
2841
|
await (0, import_promises2.rename)(tmpPath, finalPath);
|
|
2793
2842
|
}
|
|
2794
2843
|
async function readDriftState(projectRoot) {
|
|
2795
|
-
const driftPath =
|
|
2844
|
+
const driftPath = path15.join(projectRoot, ".cleargate", ".drift-state.json");
|
|
2796
2845
|
try {
|
|
2797
2846
|
const raw = await (0, import_promises2.readFile)(driftPath, "utf-8");
|
|
2798
2847
|
const parsed = JSON.parse(raw);
|
|
@@ -2867,24 +2916,24 @@ async function promptEmail(question, defaultValue, opts) {
|
|
|
2867
2916
|
|
|
2868
2917
|
// src/lib/identity.ts
|
|
2869
2918
|
init_cjs_shims();
|
|
2870
|
-
var
|
|
2871
|
-
var
|
|
2919
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
2920
|
+
var path16 = __toESM(require("path"), 1);
|
|
2872
2921
|
var fsPromises = __toESM(require("fs/promises"), 1);
|
|
2873
2922
|
var os5 = __toESM(require("os"), 1);
|
|
2874
2923
|
var import_node_child_process2 = require("child_process");
|
|
2875
2924
|
function readParticipant(projectRoot) {
|
|
2876
|
-
const filePath =
|
|
2925
|
+
const filePath = path16.join(projectRoot, ".cleargate", ".participant.json");
|
|
2877
2926
|
try {
|
|
2878
|
-
const raw =
|
|
2927
|
+
const raw = fs15.readFileSync(filePath, "utf8");
|
|
2879
2928
|
return JSON.parse(raw);
|
|
2880
2929
|
} catch {
|
|
2881
2930
|
return null;
|
|
2882
2931
|
}
|
|
2883
2932
|
}
|
|
2884
2933
|
async function writeParticipant(projectRoot, email, source, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2885
|
-
const cleargateDir =
|
|
2934
|
+
const cleargateDir = path16.join(projectRoot, ".cleargate");
|
|
2886
2935
|
await fsPromises.mkdir(cleargateDir, { recursive: true });
|
|
2887
|
-
const filePath =
|
|
2936
|
+
const filePath = path16.join(cleargateDir, ".participant.json");
|
|
2888
2937
|
const tmpPath = filePath + ".tmp." + Date.now();
|
|
2889
2938
|
const content = {
|
|
2890
2939
|
email,
|
|
@@ -2960,16 +3009,16 @@ var HOOK_ADDITION = {
|
|
|
2960
3009
|
};
|
|
2961
3010
|
function resolveDefaultPayloadDir() {
|
|
2962
3011
|
const thisFile = (0, import_node_url5.fileURLToPath)(importMetaUrl);
|
|
2963
|
-
const pkgRoot =
|
|
2964
|
-
return
|
|
3012
|
+
const pkgRoot = path17.resolve(path17.dirname(thisFile), "..");
|
|
3013
|
+
return path17.join(pkgRoot, "templates", "cleargate-planning");
|
|
2965
3014
|
}
|
|
2966
3015
|
function countDeliveryItems(cwd) {
|
|
2967
|
-
const pendingSync =
|
|
2968
|
-
const archive =
|
|
3016
|
+
const pendingSync = path17.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
3017
|
+
const archive = path17.join(cwd, ".cleargate", "delivery", "archive");
|
|
2969
3018
|
let count = 0;
|
|
2970
3019
|
for (const dir of [pendingSync, archive]) {
|
|
2971
|
-
if (!
|
|
2972
|
-
const entries =
|
|
3020
|
+
if (!fs16.existsSync(dir)) continue;
|
|
3021
|
+
const entries = fs16.readdirSync(dir);
|
|
2973
3022
|
for (const f of entries) {
|
|
2974
3023
|
if (f.endsWith(".md") && f !== ".gitkeep") count++;
|
|
2975
3024
|
}
|
|
@@ -2978,12 +3027,12 @@ function countDeliveryItems(cwd) {
|
|
|
2978
3027
|
}
|
|
2979
3028
|
function writeAtomic(filePath, content) {
|
|
2980
3029
|
const tmpPath = filePath + ".tmp." + Date.now();
|
|
2981
|
-
|
|
2982
|
-
|
|
3030
|
+
fs16.writeFileSync(tmpPath, content, "utf8");
|
|
3031
|
+
fs16.renameSync(tmpPath, filePath);
|
|
2983
3032
|
}
|
|
2984
3033
|
function readPackageVersion(packageJsonPath) {
|
|
2985
3034
|
try {
|
|
2986
|
-
const raw =
|
|
3035
|
+
const raw = fs16.readFileSync(packageJsonPath, "utf8");
|
|
2987
3036
|
const pkg = JSON.parse(raw);
|
|
2988
3037
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
2989
3038
|
return pkg.version;
|
|
@@ -3003,16 +3052,16 @@ async function initHandler(opts = {}) {
|
|
|
3003
3052
|
const promptYesNoFn = opts.promptYesNo ?? promptYesNo;
|
|
3004
3053
|
const promptEmailFn = opts.promptEmail ?? promptEmail;
|
|
3005
3054
|
const spawnSyncFn = opts.spawnSyncFn ?? import_node_child_process3.spawnSync;
|
|
3006
|
-
if (!
|
|
3055
|
+
if (!fs16.existsSync(cwd)) {
|
|
3007
3056
|
stderr(`[cleargate init] ERROR: target directory does not exist: ${cwd}
|
|
3008
3057
|
`);
|
|
3009
3058
|
exit(1);
|
|
3010
3059
|
return;
|
|
3011
3060
|
}
|
|
3012
|
-
const testWritePath =
|
|
3061
|
+
const testWritePath = path17.join(cwd, `.cleargate-init-write-test-${Date.now()}`);
|
|
3013
3062
|
try {
|
|
3014
|
-
|
|
3015
|
-
|
|
3063
|
+
fs16.writeFileSync(testWritePath, "");
|
|
3064
|
+
fs16.unlinkSync(testWritePath);
|
|
3016
3065
|
} catch {
|
|
3017
3066
|
stderr(`[cleargate init] ERROR: target directory is not writable: ${cwd}
|
|
3018
3067
|
`);
|
|
@@ -3022,7 +3071,7 @@ async function initHandler(opts = {}) {
|
|
|
3022
3071
|
stdout(`[cleargate init] Target: ${cwd}
|
|
3023
3072
|
`);
|
|
3024
3073
|
const payloadDir = opts.payloadDir ?? resolveDefaultPayloadDir();
|
|
3025
|
-
if (!
|
|
3074
|
+
if (!fs16.existsSync(payloadDir)) {
|
|
3026
3075
|
stderr(`[cleargate init] ERROR: payload directory not found: ${payloadDir}
|
|
3027
3076
|
`);
|
|
3028
3077
|
stderr(`[cleargate init] Run \`npm run prebuild\` to copy the payload first.
|
|
@@ -3030,12 +3079,12 @@ async function initHandler(opts = {}) {
|
|
|
3030
3079
|
exit(1);
|
|
3031
3080
|
return;
|
|
3032
3081
|
}
|
|
3033
|
-
const uninstalledMarkerPath =
|
|
3082
|
+
const uninstalledMarkerPath = path17.join(cwd, ".cleargate", ".uninstalled");
|
|
3034
3083
|
let uninstalledMarker = null;
|
|
3035
3084
|
let userChoseRestore = false;
|
|
3036
|
-
if (
|
|
3085
|
+
if (fs16.existsSync(uninstalledMarkerPath)) {
|
|
3037
3086
|
try {
|
|
3038
|
-
const raw =
|
|
3087
|
+
const raw = fs16.readFileSync(uninstalledMarkerPath, "utf8");
|
|
3039
3088
|
uninstalledMarker = JSON.parse(raw);
|
|
3040
3089
|
} catch {
|
|
3041
3090
|
stderr(`[cleargate init] WARNING: .uninstalled marker is malformed; ignoring it.
|
|
@@ -3047,8 +3096,8 @@ async function initHandler(opts = {}) {
|
|
|
3047
3096
|
userChoseRestore = await promptYesNoFn(question, true);
|
|
3048
3097
|
if (userChoseRestore) {
|
|
3049
3098
|
for (const preservedPath of preserved) {
|
|
3050
|
-
const absPreserved =
|
|
3051
|
-
if (
|
|
3099
|
+
const absPreserved = path17.isAbsolute(preservedPath) ? preservedPath : path17.join(cwd, preservedPath);
|
|
3100
|
+
if (fs16.existsSync(absPreserved)) {
|
|
3052
3101
|
stdout(`[cleargate init] [preserved] ${preservedPath}
|
|
3053
3102
|
`);
|
|
3054
3103
|
} else {
|
|
@@ -3068,8 +3117,8 @@ async function initHandler(opts = {}) {
|
|
|
3068
3117
|
if (opts.pin) {
|
|
3069
3118
|
pinVersion = opts.pin;
|
|
3070
3119
|
} else {
|
|
3071
|
-
const payloadParent =
|
|
3072
|
-
pinVersion = readPackageVersion(
|
|
3120
|
+
const payloadParent = path17.resolve(payloadDir, "..", "..");
|
|
3121
|
+
pinVersion = readPackageVersion(path17.join(payloadParent, "package.json")) ?? readPackageVersion(path17.join(path17.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl)), "..", "package.json")) ?? "latest";
|
|
3073
3122
|
}
|
|
3074
3123
|
const copyReport = copyPayload(payloadDir, cwd, { force, pinVersion });
|
|
3075
3124
|
for (const action of copyReport.actions) {
|
|
@@ -3077,33 +3126,33 @@ async function initHandler(opts = {}) {
|
|
|
3077
3126
|
stdout(`[cleargate init] ${verb} ${action.relPath}
|
|
3078
3127
|
`);
|
|
3079
3128
|
}
|
|
3080
|
-
const settingsPath =
|
|
3129
|
+
const settingsPath = path17.join(cwd, ".claude", "settings.json");
|
|
3081
3130
|
let existingSettings = null;
|
|
3082
|
-
if (
|
|
3131
|
+
if (fs16.existsSync(settingsPath)) {
|
|
3083
3132
|
try {
|
|
3084
|
-
existingSettings = JSON.parse(
|
|
3133
|
+
existingSettings = JSON.parse(fs16.readFileSync(settingsPath, "utf8"));
|
|
3085
3134
|
} catch {
|
|
3086
3135
|
stderr(`[cleargate init] WARNING: could not parse ${settingsPath}; treating as empty.
|
|
3087
3136
|
`);
|
|
3088
3137
|
}
|
|
3089
3138
|
}
|
|
3090
3139
|
const mergedSettings = mergeSettings(existingSettings, HOOK_ADDITION);
|
|
3091
|
-
|
|
3140
|
+
fs16.mkdirSync(path17.dirname(settingsPath), { recursive: true });
|
|
3092
3141
|
writeAtomic(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
|
|
3093
3142
|
stdout(`[cleargate init] Updated .claude/settings.json: merged PostToolUse hook
|
|
3094
3143
|
`);
|
|
3095
|
-
const claudeMdPath =
|
|
3096
|
-
const claudeMdSrcPath =
|
|
3144
|
+
const claudeMdPath = path17.join(cwd, "CLAUDE.md");
|
|
3145
|
+
const claudeMdSrcPath = path17.join(payloadDir, "CLAUDE.md");
|
|
3097
3146
|
let claudeMdBlock;
|
|
3098
3147
|
try {
|
|
3099
|
-
const claudeMdSrc =
|
|
3148
|
+
const claudeMdSrc = fs16.readFileSync(claudeMdSrcPath, "utf8");
|
|
3100
3149
|
claudeMdBlock = extractBlock(claudeMdSrc);
|
|
3101
3150
|
} catch (e) {
|
|
3102
3151
|
stderr(`[cleargate init] WARNING: could not read CLAUDE.md block from payload: ${String(e)}
|
|
3103
3152
|
`);
|
|
3104
3153
|
claudeMdBlock = "<!-- CLEARGATE:START -->\n<!-- CLEARGATE:END -->";
|
|
3105
3154
|
}
|
|
3106
|
-
const existingClaudeMd =
|
|
3155
|
+
const existingClaudeMd = fs16.existsSync(claudeMdPath) ? fs16.readFileSync(claudeMdPath, "utf8") : null;
|
|
3107
3156
|
const newClaudeMd = injectClaudeMd(existingClaudeMd, claudeMdBlock);
|
|
3108
3157
|
writeAtomic(claudeMdPath, newClaudeMd);
|
|
3109
3158
|
if (existingClaudeMd === null) {
|
|
@@ -3114,6 +3163,26 @@ async function initHandler(opts = {}) {
|
|
|
3114
3163
|
`);
|
|
3115
3164
|
} else {
|
|
3116
3165
|
stdout(`[cleargate init] CLAUDE.md unchanged (block already up to date)
|
|
3166
|
+
`);
|
|
3167
|
+
}
|
|
3168
|
+
try {
|
|
3169
|
+
const action = injectMcpJson(cwd);
|
|
3170
|
+
if (action === "created") {
|
|
3171
|
+
stdout(
|
|
3172
|
+
`[cleargate init] Created .mcp.json (cleargate MCP server registered) \u2014 restart Claude Code to load it.
|
|
3173
|
+
`
|
|
3174
|
+
);
|
|
3175
|
+
} else if (action === "updated") {
|
|
3176
|
+
stdout(
|
|
3177
|
+
`[cleargate init] Updated .mcp.json (cleargate MCP server entry merged) \u2014 restart Claude Code to pick up changes.
|
|
3178
|
+
`
|
|
3179
|
+
);
|
|
3180
|
+
} else {
|
|
3181
|
+
stdout(`[cleargate init] .mcp.json unchanged (cleargate entry already present)
|
|
3182
|
+
`);
|
|
3183
|
+
}
|
|
3184
|
+
} catch (e) {
|
|
3185
|
+
stderr(`[cleargate init] WARNING: ${String(e instanceof Error ? e.message : e)}
|
|
3117
3186
|
`);
|
|
3118
3187
|
}
|
|
3119
3188
|
const itemCount = countDeliveryItems(cwd);
|
|
@@ -3127,9 +3196,9 @@ async function initHandler(opts = {}) {
|
|
|
3127
3196
|
stdout(`[cleargate init] Bootstrap: no items to ingest, skipping build
|
|
3128
3197
|
`);
|
|
3129
3198
|
}
|
|
3130
|
-
const cleargateDir =
|
|
3131
|
-
|
|
3132
|
-
const snapshotPath =
|
|
3199
|
+
const cleargateDir = path17.join(cwd, ".cleargate");
|
|
3200
|
+
fs16.mkdirSync(cleargateDir, { recursive: true });
|
|
3201
|
+
const snapshotPath = path17.join(cleargateDir, ".install-manifest.json");
|
|
3133
3202
|
try {
|
|
3134
3203
|
const readManifest = opts.readInstallManifest ?? (() => loadPackageManifest({ packageRoot: payloadDir }));
|
|
3135
3204
|
const pkgManifest = readManifest();
|
|
@@ -3144,19 +3213,19 @@ async function initHandler(opts = {}) {
|
|
|
3144
3213
|
stderr(`[cleargate init] WARNING: could not write install snapshot: ${String(e)}
|
|
3145
3214
|
`);
|
|
3146
3215
|
}
|
|
3147
|
-
if (uninstalledMarker !== null &&
|
|
3216
|
+
if (uninstalledMarker !== null && fs16.existsSync(uninstalledMarkerPath)) {
|
|
3148
3217
|
try {
|
|
3149
|
-
|
|
3218
|
+
fs16.unlinkSync(uninstalledMarkerPath);
|
|
3150
3219
|
} catch (e) {
|
|
3151
3220
|
stderr(`[cleargate init] WARNING: could not remove .uninstalled marker: ${String(e)}
|
|
3152
3221
|
`);
|
|
3153
3222
|
}
|
|
3154
3223
|
}
|
|
3155
3224
|
{
|
|
3156
|
-
const distCliPath =
|
|
3225
|
+
const distCliPath = path17.join(cwd, "cleargate-cli", "dist", "cli.js");
|
|
3157
3226
|
let branch = null;
|
|
3158
3227
|
let branchLabel = "";
|
|
3159
|
-
if (
|
|
3228
|
+
if (fs16.existsSync(distCliPath)) {
|
|
3160
3229
|
branch = { cmd: "node", args: [distCliPath, "--version"] };
|
|
3161
3230
|
branchLabel = `local dist (${distCliPath})`;
|
|
3162
3231
|
} else {
|
|
@@ -3228,8 +3297,8 @@ async function initHandler(opts = {}) {
|
|
|
3228
3297
|
|
|
3229
3298
|
// src/commands/wiki-ingest.ts
|
|
3230
3299
|
init_cjs_shims();
|
|
3231
|
-
var
|
|
3232
|
-
var
|
|
3300
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
3301
|
+
var path18 = __toESM(require("path"), 1);
|
|
3233
3302
|
var import_node_child_process4 = require("child_process");
|
|
3234
3303
|
var EXCLUDED_SUFFIXES2 = [
|
|
3235
3304
|
".cleargate/knowledge/",
|
|
@@ -3255,16 +3324,16 @@ async function wikiIngestHandler(opts) {
|
|
|
3255
3324
|
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
3256
3325
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
3257
3326
|
const gitRunner = opts.gitRunner;
|
|
3258
|
-
const rename12 = opts.rename ??
|
|
3327
|
+
const rename12 = opts.rename ?? fs17.renameSync;
|
|
3259
3328
|
const templateDir = opts.templateDir;
|
|
3260
3329
|
const rawPath = opts.rawPath;
|
|
3261
|
-
const absRawPath =
|
|
3262
|
-
const relRawPath =
|
|
3263
|
-
const deliveryRoot =
|
|
3330
|
+
const absRawPath = path18.isAbsolute(rawPath) ? rawPath : path18.resolve(cwd, rawPath);
|
|
3331
|
+
const relRawPath = path18.relative(cwd, absRawPath).replace(/\\/g, "/");
|
|
3332
|
+
const deliveryRoot = path18.join(cwd, ".cleargate", "delivery");
|
|
3264
3333
|
const deliveryRootNorm = deliveryRoot.replace(/\\/g, "/");
|
|
3265
3334
|
const absDeliveryRoot = deliveryRoot;
|
|
3266
|
-
const relToDelivery =
|
|
3267
|
-
if (relToDelivery.startsWith("..") ||
|
|
3335
|
+
const relToDelivery = path18.relative(absDeliveryRoot, absRawPath);
|
|
3336
|
+
if (relToDelivery.startsWith("..") || path18.isAbsolute(relToDelivery)) {
|
|
3268
3337
|
stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
|
|
3269
3338
|
`);
|
|
3270
3339
|
exit(2);
|
|
@@ -3278,7 +3347,7 @@ async function wikiIngestHandler(opts) {
|
|
|
3278
3347
|
exit(0);
|
|
3279
3348
|
return;
|
|
3280
3349
|
}
|
|
3281
|
-
const filename =
|
|
3350
|
+
const filename = path18.basename(absRawPath);
|
|
3282
3351
|
let bucketInfo;
|
|
3283
3352
|
try {
|
|
3284
3353
|
bucketInfo = deriveBucket(filename);
|
|
@@ -3298,12 +3367,12 @@ async function wikiIngestHandler(opts) {
|
|
|
3298
3367
|
return;
|
|
3299
3368
|
}
|
|
3300
3369
|
const { type, id, bucket } = bucketInfo;
|
|
3301
|
-
const wikiRoot =
|
|
3302
|
-
const pageDir =
|
|
3303
|
-
const pagePath =
|
|
3370
|
+
const wikiRoot = path18.join(cwd, ".cleargate", "wiki");
|
|
3371
|
+
const pageDir = path18.join(wikiRoot, bucket);
|
|
3372
|
+
const pagePath = path18.join(pageDir, `${id}.md`);
|
|
3304
3373
|
let rawContent;
|
|
3305
3374
|
try {
|
|
3306
|
-
rawContent =
|
|
3375
|
+
rawContent = fs17.readFileSync(absRawPath, "utf8");
|
|
3307
3376
|
} catch (e) {
|
|
3308
3377
|
stderr(`wiki ingest: cannot read ${rawPath}: ${e.message}
|
|
3309
3378
|
`);
|
|
@@ -3323,11 +3392,11 @@ async function wikiIngestHandler(opts) {
|
|
|
3323
3392
|
return;
|
|
3324
3393
|
}
|
|
3325
3394
|
const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
|
|
3326
|
-
const pageExists =
|
|
3395
|
+
const pageExists = fs17.existsSync(pagePath);
|
|
3327
3396
|
if (pageExists && currentSha !== "") {
|
|
3328
3397
|
let isNoOp = false;
|
|
3329
3398
|
try {
|
|
3330
|
-
const existingPageContent =
|
|
3399
|
+
const existingPageContent = fs17.readFileSync(pagePath, "utf8");
|
|
3331
3400
|
const existingPage = parsePage(existingPageContent);
|
|
3332
3401
|
if (existingPage.last_ingest_commit === currentSha) {
|
|
3333
3402
|
const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
|
|
@@ -3362,8 +3431,8 @@ async function wikiIngestHandler(opts) {
|
|
|
3362
3431
|
};
|
|
3363
3432
|
const pageBody = buildPageBody2({ id, fm, body });
|
|
3364
3433
|
const pageContent = serializePage(wikiPage, pageBody);
|
|
3365
|
-
|
|
3366
|
-
|
|
3434
|
+
fs17.mkdirSync(pageDir, { recursive: true });
|
|
3435
|
+
fs17.writeFileSync(pagePath, pageContent, "utf8");
|
|
3367
3436
|
appendLogEntry(wikiRoot, { timestamp, action, id, relRawPath });
|
|
3368
3437
|
updateIndex(wikiRoot, { id, type, status: wikiPage.status, relRawPath, rename: rename12 });
|
|
3369
3438
|
recompileSynthesis(wikiRoot, cwd, templateDir);
|
|
@@ -3375,7 +3444,7 @@ function checkContentUnchanged(absRawPath, sha, relRawPath, gitRunner) {
|
|
|
3375
3444
|
const run = gitRunner ?? defaultGitRunner;
|
|
3376
3445
|
const gitContent = run("git", ["show", `${sha}:${relRawPath}`]);
|
|
3377
3446
|
if (!gitContent && gitContent !== "") return false;
|
|
3378
|
-
const currentContent =
|
|
3447
|
+
const currentContent = fs17.readFileSync(absRawPath, "utf8");
|
|
3379
3448
|
return gitContent === currentContent;
|
|
3380
3449
|
} catch {
|
|
3381
3450
|
return false;
|
|
@@ -3428,7 +3497,7 @@ function buildPageBody2(item) {
|
|
|
3428
3497
|
].join("\n");
|
|
3429
3498
|
}
|
|
3430
3499
|
function appendLogEntry(wikiRoot, entry) {
|
|
3431
|
-
const logPath =
|
|
3500
|
+
const logPath = path18.join(wikiRoot, "log.md");
|
|
3432
3501
|
const logEntry = [
|
|
3433
3502
|
`- timestamp: "${entry.timestamp}"`,
|
|
3434
3503
|
` actor: "cleargate wiki ingest"`,
|
|
@@ -3436,25 +3505,25 @@ function appendLogEntry(wikiRoot, entry) {
|
|
|
3436
3505
|
` target: "${entry.id}"`,
|
|
3437
3506
|
` path: "${entry.relRawPath}"`
|
|
3438
3507
|
].join("\n");
|
|
3439
|
-
if (
|
|
3440
|
-
const existing =
|
|
3508
|
+
if (fs17.existsSync(logPath)) {
|
|
3509
|
+
const existing = fs17.readFileSync(logPath, "utf8");
|
|
3441
3510
|
const newContent = existing.trimEnd() + "\n" + logEntry + "\n";
|
|
3442
|
-
|
|
3511
|
+
fs17.writeFileSync(logPath, newContent, "utf8");
|
|
3443
3512
|
} else {
|
|
3444
|
-
|
|
3445
|
-
|
|
3513
|
+
fs17.mkdirSync(wikiRoot, { recursive: true });
|
|
3514
|
+
fs17.writeFileSync(logPath, `# Wiki Event Log
|
|
3446
3515
|
|
|
3447
3516
|
${logEntry}
|
|
3448
3517
|
`, "utf8");
|
|
3449
3518
|
}
|
|
3450
3519
|
}
|
|
3451
3520
|
function updateIndex(wikiRoot, opts) {
|
|
3452
|
-
const indexPath =
|
|
3521
|
+
const indexPath = path18.join(wikiRoot, "index.md");
|
|
3453
3522
|
const tmpPath = `${indexPath}.tmp`;
|
|
3454
3523
|
const newRow = `| [[${opts.id}]] | ${opts.type} | ${opts.status} | ${opts.relRawPath} |`;
|
|
3455
3524
|
let content;
|
|
3456
|
-
if (
|
|
3457
|
-
content =
|
|
3525
|
+
if (fs17.existsSync(indexPath)) {
|
|
3526
|
+
content = fs17.readFileSync(indexPath, "utf8");
|
|
3458
3527
|
const idPattern = `[[${opts.id}]]`;
|
|
3459
3528
|
const lines = content.split("\n");
|
|
3460
3529
|
let replaced = false;
|
|
@@ -3473,7 +3542,7 @@ function updateIndex(wikiRoot, opts) {
|
|
|
3473
3542
|
} else {
|
|
3474
3543
|
content = buildMinimalIndex(opts.id, opts.type, opts.status, opts.relRawPath);
|
|
3475
3544
|
}
|
|
3476
|
-
|
|
3545
|
+
fs17.writeFileSync(tmpPath, content, "utf8");
|
|
3477
3546
|
opts.rename(tmpPath, indexPath);
|
|
3478
3547
|
}
|
|
3479
3548
|
function insertIntoSection(content, id, newRow) {
|
|
@@ -3568,41 +3637,41 @@ function buildMinimalIndex(id, type, status, relRawPath) {
|
|
|
3568
3637
|
return lines.join("\n");
|
|
3569
3638
|
}
|
|
3570
3639
|
function recompileSynthesis(wikiRoot, cwd, templateDir) {
|
|
3571
|
-
const deliveryRoot =
|
|
3640
|
+
const deliveryRoot = path18.join(cwd, ".cleargate", "delivery");
|
|
3572
3641
|
let items = [];
|
|
3573
|
-
if (
|
|
3642
|
+
if (fs17.existsSync(deliveryRoot)) {
|
|
3574
3643
|
try {
|
|
3575
3644
|
items = scanRawItems(deliveryRoot, cwd);
|
|
3576
3645
|
} catch {
|
|
3577
3646
|
}
|
|
3578
3647
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3648
|
+
fs17.writeFileSync(path18.join(wikiRoot, "active-sprint.md"), compile(items, templateDir), "utf8");
|
|
3649
|
+
fs17.writeFileSync(path18.join(wikiRoot, "open-gates.md"), compile2(items, templateDir), "utf8");
|
|
3650
|
+
fs17.writeFileSync(path18.join(wikiRoot, "product-state.md"), compile3(items, templateDir), "utf8");
|
|
3651
|
+
fs17.writeFileSync(path18.join(wikiRoot, "roadmap.md"), compile4(items, templateDir), "utf8");
|
|
3583
3652
|
}
|
|
3584
3653
|
|
|
3585
3654
|
// src/commands/wiki-lint.ts
|
|
3586
3655
|
init_cjs_shims();
|
|
3587
|
-
var
|
|
3656
|
+
var path22 = __toESM(require("path"), 1);
|
|
3588
3657
|
|
|
3589
3658
|
// src/wiki/load-wiki.ts
|
|
3590
3659
|
init_cjs_shims();
|
|
3591
|
-
var
|
|
3592
|
-
var
|
|
3660
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
3661
|
+
var path19 = __toESM(require("path"), 1);
|
|
3593
3662
|
var BUCKET_DIRS = ["epics", "stories", "sprints", "proposals", "crs", "bugs", "topics"];
|
|
3594
3663
|
function loadWikiPages(wikiRoot) {
|
|
3595
3664
|
const results = [];
|
|
3596
3665
|
for (const bucket of BUCKET_DIRS) {
|
|
3597
|
-
const dir =
|
|
3598
|
-
if (!
|
|
3599
|
-
const entries =
|
|
3666
|
+
const dir = path19.join(wikiRoot, bucket);
|
|
3667
|
+
if (!fs18.existsSync(dir)) continue;
|
|
3668
|
+
const entries = fs18.readdirSync(dir, { encoding: "utf8" });
|
|
3600
3669
|
for (const filename of entries) {
|
|
3601
3670
|
if (!filename.endsWith(".md")) continue;
|
|
3602
|
-
const absPath =
|
|
3603
|
-
const stat =
|
|
3671
|
+
const absPath = path19.join(dir, filename);
|
|
3672
|
+
const stat = fs18.statSync(absPath);
|
|
3604
3673
|
if (!stat.isFile()) continue;
|
|
3605
|
-
const raw =
|
|
3674
|
+
const raw = fs18.readFileSync(absPath, "utf8");
|
|
3606
3675
|
let fm;
|
|
3607
3676
|
let body;
|
|
3608
3677
|
try {
|
|
@@ -3632,8 +3701,8 @@ function loadWikiPages(wikiRoot) {
|
|
|
3632
3701
|
|
|
3633
3702
|
// src/wiki/lint-checks.ts
|
|
3634
3703
|
init_cjs_shims();
|
|
3635
|
-
var
|
|
3636
|
-
var
|
|
3704
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
3705
|
+
var path20 = __toESM(require("path"), 1);
|
|
3637
3706
|
var import_node_child_process5 = require("child_process");
|
|
3638
3707
|
var import_js_yaml3 = __toESM(require("js-yaml"), 1);
|
|
3639
3708
|
|
|
@@ -3693,9 +3762,9 @@ function checkOrphan(page, repoRoot) {
|
|
|
3693
3762
|
if (!rawPath) return null;
|
|
3694
3763
|
const isExcluded = EXCLUDED_DIRS.some((excl) => rawPath.startsWith(excl));
|
|
3695
3764
|
if (isExcluded) return null;
|
|
3696
|
-
const absRaw =
|
|
3697
|
-
if (!
|
|
3698
|
-
const relPage =
|
|
3765
|
+
const absRaw = path20.join(repoRoot, rawPath);
|
|
3766
|
+
if (!fs19.existsSync(absRaw)) {
|
|
3767
|
+
const relPage = path20.relative(path20.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3699
3768
|
return {
|
|
3700
3769
|
category: "orphan",
|
|
3701
3770
|
line: `orphan: ${relPage} -> missing ${rawPath} (raw missing)`
|
|
@@ -3713,7 +3782,7 @@ function checkRepoMismatch(page, repoRoot) {
|
|
|
3713
3782
|
return null;
|
|
3714
3783
|
}
|
|
3715
3784
|
if (page.page.repo !== derivedRepo) {
|
|
3716
|
-
const relPage =
|
|
3785
|
+
const relPage = path20.relative(path20.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3717
3786
|
return {
|
|
3718
3787
|
category: "repo-mismatch",
|
|
3719
3788
|
line: `repo-mismatch: ${relPage} declares repo:${page.page.repo} but raw_path implies repo:${derivedRepo}`
|
|
@@ -3738,7 +3807,7 @@ function checkStaleCommit(page, repoRoot, gitRunner) {
|
|
|
3738
3807
|
}
|
|
3739
3808
|
if (!currentSha) return null;
|
|
3740
3809
|
if (storedSha !== currentSha) {
|
|
3741
|
-
const relPage =
|
|
3810
|
+
const relPage = path20.relative(path20.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3742
3811
|
return {
|
|
3743
3812
|
category: "stale-commit",
|
|
3744
3813
|
line: `stale-commit: ${relPage} at ${storedSha}, current ${currentSha}`
|
|
@@ -3749,14 +3818,14 @@ function checkStaleCommit(page, repoRoot, gitRunner) {
|
|
|
3749
3818
|
function checkMissingIngest(page, repoRoot) {
|
|
3750
3819
|
const rawPath = page.page.raw_path;
|
|
3751
3820
|
if (!rawPath) return null;
|
|
3752
|
-
const absRaw =
|
|
3753
|
-
if (!
|
|
3754
|
-
const rawStat =
|
|
3755
|
-
const pageStat =
|
|
3821
|
+
const absRaw = path20.join(repoRoot, rawPath);
|
|
3822
|
+
if (!fs19.existsSync(absRaw)) return null;
|
|
3823
|
+
const rawStat = fs19.statSync(absRaw);
|
|
3824
|
+
const pageStat = fs19.statSync(page.absPath);
|
|
3756
3825
|
const rawMtimeMs = rawStat.mtimeMs;
|
|
3757
3826
|
const pageMtimeMs = pageStat.mtimeMs;
|
|
3758
3827
|
if (rawMtimeMs - pageMtimeMs > 2e3) {
|
|
3759
|
-
const relPage =
|
|
3828
|
+
const relPage = path20.relative(path20.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3760
3829
|
const rawMtime = rawStat.mtime.toISOString();
|
|
3761
3830
|
const pageMtime = pageStat.mtime.toISOString();
|
|
3762
3831
|
return {
|
|
@@ -3767,7 +3836,7 @@ function checkMissingIngest(page, repoRoot) {
|
|
|
3767
3836
|
return null;
|
|
3768
3837
|
}
|
|
3769
3838
|
function checkBrokenBacklinks(pages, repoRoot) {
|
|
3770
|
-
const wikiRoot =
|
|
3839
|
+
const wikiRoot = path20.join(repoRoot, ".cleargate", "wiki");
|
|
3771
3840
|
const byId = /* @__PURE__ */ new Map();
|
|
3772
3841
|
for (const p of pages) {
|
|
3773
3842
|
if (p.page.id) byId.set(p.page.id, p);
|
|
@@ -3781,7 +3850,7 @@ function checkBrokenBacklinks(pages, repoRoot) {
|
|
|
3781
3850
|
const parentId = match[1];
|
|
3782
3851
|
const parentPage = byId.get(parentId);
|
|
3783
3852
|
if (!parentPage) {
|
|
3784
|
-
const relChild =
|
|
3853
|
+
const relChild = path20.relative(wikiRoot, childPage.absPath).replace(/\\/g, "/");
|
|
3785
3854
|
findings.push({
|
|
3786
3855
|
category: "broken-backlink",
|
|
3787
3856
|
line: `broken-backlink: ${relChild} -> ${parentId} (parent missing child entry)`
|
|
@@ -3794,7 +3863,7 @@ function checkBrokenBacklinks(pages, repoRoot) {
|
|
|
3794
3863
|
(c) => c === childRef || c === childId
|
|
3795
3864
|
);
|
|
3796
3865
|
if (!parentHasChild) {
|
|
3797
|
-
const relChild =
|
|
3866
|
+
const relChild = path20.relative(wikiRoot, childPage.absPath).replace(/\\/g, "/");
|
|
3798
3867
|
findings.push({
|
|
3799
3868
|
category: "broken-backlink",
|
|
3800
3869
|
line: `broken-backlink: ${relChild} -> ${parentId} (parent missing child entry)`
|
|
@@ -3804,7 +3873,7 @@ function checkBrokenBacklinks(pages, repoRoot) {
|
|
|
3804
3873
|
return findings;
|
|
3805
3874
|
}
|
|
3806
3875
|
function checkInvalidatedCitations(pages, repoRoot) {
|
|
3807
|
-
const wikiRoot =
|
|
3876
|
+
const wikiRoot = path20.join(repoRoot, ".cleargate", "wiki");
|
|
3808
3877
|
const byId = /* @__PURE__ */ new Map();
|
|
3809
3878
|
for (const p of pages) {
|
|
3810
3879
|
if (p.page.id) byId.set(p.page.id, p);
|
|
@@ -3812,10 +3881,10 @@ function checkInvalidatedCitations(pages, repoRoot) {
|
|
|
3812
3881
|
const findings = [];
|
|
3813
3882
|
const topicPages = pages.filter((p) => p.page.type === "topic");
|
|
3814
3883
|
for (const topicPage of topicPages) {
|
|
3815
|
-
const relTopic =
|
|
3884
|
+
const relTopic = path20.relative(wikiRoot, topicPage.absPath).replace(/\\/g, "/");
|
|
3816
3885
|
let citesList = [];
|
|
3817
3886
|
try {
|
|
3818
|
-
const raw =
|
|
3887
|
+
const raw = fs19.readFileSync(topicPage.absPath, "utf8");
|
|
3819
3888
|
const { fm } = parseFrontmatter(raw);
|
|
3820
3889
|
const rawCites = fm["cites"];
|
|
3821
3890
|
if (Array.isArray(rawCites)) {
|
|
@@ -3851,7 +3920,7 @@ function checkExcludedPathIngested(page, repoRoot) {
|
|
|
3851
3920
|
if (!rawPath) return null;
|
|
3852
3921
|
const isExcluded = EXCLUDED_DIRS.some((excl) => rawPath.startsWith(excl));
|
|
3853
3922
|
if (isExcluded) {
|
|
3854
|
-
const relPage =
|
|
3923
|
+
const relPage = path20.relative(path20.join(repoRoot, ".cleargate", "wiki"), page.absPath).replace(/\\/g, "/");
|
|
3855
3924
|
return {
|
|
3856
3925
|
category: "excluded-path-ingested",
|
|
3857
3926
|
line: `excluded-path-ingested: ${relPage} (raw_path ${rawPath} is under an excluded directory)`
|
|
@@ -3862,7 +3931,7 @@ function checkExcludedPathIngested(page, repoRoot) {
|
|
|
3862
3931
|
function checkPaginationNeeded(pages) {
|
|
3863
3932
|
const bucketCounts = /* @__PURE__ */ new Map();
|
|
3864
3933
|
for (const p of pages) {
|
|
3865
|
-
const bucket =
|
|
3934
|
+
const bucket = path20.basename(path20.dirname(p.absPath));
|
|
3866
3935
|
bucketCounts.set(bucket, (bucketCounts.get(bucket) ?? 0) + 1);
|
|
3867
3936
|
}
|
|
3868
3937
|
const findings = [];
|
|
@@ -3900,11 +3969,11 @@ function parseCachedGateResult(raw) {
|
|
|
3900
3969
|
function checkGateFailure(page, repoRoot) {
|
|
3901
3970
|
const rawPath = page.page.raw_path;
|
|
3902
3971
|
if (!rawPath) return null;
|
|
3903
|
-
const absRaw =
|
|
3904
|
-
if (!
|
|
3972
|
+
const absRaw = path20.join(repoRoot, rawPath);
|
|
3973
|
+
if (!fs19.existsSync(absRaw)) return null;
|
|
3905
3974
|
let rawFm;
|
|
3906
3975
|
try {
|
|
3907
|
-
const raw =
|
|
3976
|
+
const raw = fs19.readFileSync(absRaw, "utf8");
|
|
3908
3977
|
const { fm } = parseFrontmatter(raw);
|
|
3909
3978
|
rawFm = fm;
|
|
3910
3979
|
} catch {
|
|
@@ -3938,11 +4007,11 @@ function checkGateFailure(page, repoRoot) {
|
|
|
3938
4007
|
function checkGateStaleness(page, repoRoot) {
|
|
3939
4008
|
const rawPath = page.page.raw_path;
|
|
3940
4009
|
if (!rawPath) return null;
|
|
3941
|
-
const absRaw =
|
|
3942
|
-
if (!
|
|
4010
|
+
const absRaw = path20.join(repoRoot, rawPath);
|
|
4011
|
+
if (!fs19.existsSync(absRaw)) return null;
|
|
3943
4012
|
let rawFm;
|
|
3944
4013
|
try {
|
|
3945
|
-
const raw =
|
|
4014
|
+
const raw = fs19.readFileSync(absRaw, "utf8");
|
|
3946
4015
|
const { fm } = parseFrontmatter(raw);
|
|
3947
4016
|
rawFm = fm;
|
|
3948
4017
|
} catch {
|
|
@@ -3965,7 +4034,7 @@ function checkGateStaleness(page, repoRoot) {
|
|
|
3965
4034
|
return null;
|
|
3966
4035
|
}
|
|
3967
4036
|
function discoverPlainTextMentions(pages, repoRoot) {
|
|
3968
|
-
const wikiRoot =
|
|
4037
|
+
const wikiRoot = path20.join(repoRoot, ".cleargate", "wiki");
|
|
3969
4038
|
const byId = /* @__PURE__ */ new Map();
|
|
3970
4039
|
for (const p of pages) {
|
|
3971
4040
|
if (p.page.id) byId.set(p.page.id, true);
|
|
@@ -3974,7 +4043,7 @@ function discoverPlainTextMentions(pages, repoRoot) {
|
|
|
3974
4043
|
const ID_PATTERN = /\b((?:EPIC|STORY|SPRINT|PROPOSAL|CR|BUG)-[\w-]+)\b/g;
|
|
3975
4044
|
const LINK_PATTERN = /\[\[[\w-]+\]\]/g;
|
|
3976
4045
|
for (const page of pages) {
|
|
3977
|
-
const relPage =
|
|
4046
|
+
const relPage = path20.relative(wikiRoot, page.absPath).replace(/\\/g, "/");
|
|
3978
4047
|
const wrappedRefs = /* @__PURE__ */ new Set();
|
|
3979
4048
|
for (const m of page.body.matchAll(LINK_PATTERN)) {
|
|
3980
4049
|
const inner = m[0].slice(2, -2);
|
|
@@ -3991,11 +4060,11 @@ function discoverPlainTextMentions(pages, repoRoot) {
|
|
|
3991
4060
|
return suggestions;
|
|
3992
4061
|
}
|
|
3993
4062
|
function checkIndexBudget(repoRoot, indexTokenCeiling) {
|
|
3994
|
-
const indexPath =
|
|
3995
|
-
if (!
|
|
4063
|
+
const indexPath = path20.join(repoRoot, ".cleargate", "wiki", "index.md");
|
|
4064
|
+
if (!fs19.existsSync(indexPath)) {
|
|
3996
4065
|
return { finding: null };
|
|
3997
4066
|
}
|
|
3998
|
-
const bytes =
|
|
4067
|
+
const bytes = fs19.statSync(indexPath).size;
|
|
3999
4068
|
const tokens = Math.round(bytes / 4);
|
|
4000
4069
|
const ceiling = indexTokenCeiling;
|
|
4001
4070
|
if (tokens > ceiling) {
|
|
@@ -4013,18 +4082,18 @@ function checkIndexBudget(repoRoot, indexTokenCeiling) {
|
|
|
4013
4082
|
|
|
4014
4083
|
// src/lib/wiki-config.ts
|
|
4015
4084
|
init_cjs_shims();
|
|
4016
|
-
var
|
|
4017
|
-
var
|
|
4085
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
4086
|
+
var path21 = __toESM(require("path"), 1);
|
|
4018
4087
|
var import_js_yaml4 = __toESM(require("js-yaml"), 1);
|
|
4019
4088
|
var DEFAULT_INDEX_TOKEN_CEILING = 8e3;
|
|
4020
4089
|
function loadWikiConfig(repoRoot) {
|
|
4021
|
-
const configPath =
|
|
4022
|
-
if (!
|
|
4090
|
+
const configPath = path21.join(repoRoot, ".cleargate", "config.yml");
|
|
4091
|
+
if (!fs20.existsSync(configPath)) {
|
|
4023
4092
|
return { wiki: { index_token_ceiling: DEFAULT_INDEX_TOKEN_CEILING }, gates: {} };
|
|
4024
4093
|
}
|
|
4025
4094
|
let raw;
|
|
4026
4095
|
try {
|
|
4027
|
-
raw =
|
|
4096
|
+
raw = fs20.readFileSync(configPath, "utf8");
|
|
4028
4097
|
} catch (err) {
|
|
4029
4098
|
throw new Error(`Failed to read ${configPath}: ${String(err)}`);
|
|
4030
4099
|
}
|
|
@@ -4085,7 +4154,7 @@ async function wikiLintHandler(opts = {}) {
|
|
|
4085
4154
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
4086
4155
|
const gitRunner = opts.gitRunner;
|
|
4087
4156
|
const mode = opts.mode ?? "enforce";
|
|
4088
|
-
const wikiRoot =
|
|
4157
|
+
const wikiRoot = path22.join(cwd, ".cleargate", "wiki");
|
|
4089
4158
|
const repoRoot = cwd;
|
|
4090
4159
|
let pages = loadWikiPages(wikiRoot);
|
|
4091
4160
|
const findings = [];
|
|
@@ -4157,8 +4226,8 @@ async function wikiLintHandler(opts = {}) {
|
|
|
4157
4226
|
|
|
4158
4227
|
// src/commands/wiki-query.ts
|
|
4159
4228
|
init_cjs_shims();
|
|
4160
|
-
var
|
|
4161
|
-
var
|
|
4229
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
4230
|
+
var path23 = __toESM(require("path"), 1);
|
|
4162
4231
|
function computeSlug(query) {
|
|
4163
4232
|
return query.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 40).replace(/-+$/, "");
|
|
4164
4233
|
}
|
|
@@ -4188,9 +4257,9 @@ async function wikiQueryHandler(opts) {
|
|
|
4188
4257
|
const query = opts.query;
|
|
4189
4258
|
const persist = opts.persist ?? false;
|
|
4190
4259
|
void stderr;
|
|
4191
|
-
const wikiRoot =
|
|
4192
|
-
const indexPath =
|
|
4193
|
-
if (!
|
|
4260
|
+
const wikiRoot = path23.join(cwd, ".cleargate", "wiki");
|
|
4261
|
+
const indexPath = path23.join(wikiRoot, "index.md");
|
|
4262
|
+
if (!fs21.existsSync(indexPath)) {
|
|
4194
4263
|
stdout(`wiki query: no index.md found at ${indexPath}
|
|
4195
4264
|
`);
|
|
4196
4265
|
stdout(`Run \`cleargate wiki build\` first.
|
|
@@ -4198,7 +4267,7 @@ async function wikiQueryHandler(opts) {
|
|
|
4198
4267
|
exit(1);
|
|
4199
4268
|
return;
|
|
4200
4269
|
}
|
|
4201
|
-
const indexContent =
|
|
4270
|
+
const indexContent = fs21.readFileSync(indexPath, "utf8");
|
|
4202
4271
|
const matches = searchIndex(indexContent, query);
|
|
4203
4272
|
if (matches.length === 0) {
|
|
4204
4273
|
stdout(`wiki query: no matches for "${query}"
|
|
@@ -4223,8 +4292,8 @@ async function wikiQueryHandler(opts) {
|
|
|
4223
4292
|
return;
|
|
4224
4293
|
}
|
|
4225
4294
|
const slug = computeSlug(query);
|
|
4226
|
-
const topicsDir =
|
|
4227
|
-
|
|
4295
|
+
const topicsDir = path23.join(wikiRoot, "topics");
|
|
4296
|
+
fs21.mkdirSync(topicsDir, { recursive: true });
|
|
4228
4297
|
const citesArray = matches.map(({ id }) => `"[[${id}]]"`);
|
|
4229
4298
|
const createdAt = now();
|
|
4230
4299
|
const frontmatter = [
|
|
@@ -4239,13 +4308,13 @@ async function wikiQueryHandler(opts) {
|
|
|
4239
4308
|
const topicContent = `${frontmatter}
|
|
4240
4309
|
|
|
4241
4310
|
${body}`;
|
|
4242
|
-
const topicPath =
|
|
4243
|
-
|
|
4311
|
+
const topicPath = path23.join(topicsDir, `${slug}.md`);
|
|
4312
|
+
fs21.writeFileSync(topicPath, topicContent, "utf8");
|
|
4244
4313
|
updateIndexTopicsSection(indexPath, slug, query, createdAt);
|
|
4245
4314
|
exit(0);
|
|
4246
4315
|
}
|
|
4247
4316
|
function updateIndexTopicsSection(indexPath, slug, query, createdAt) {
|
|
4248
|
-
let content =
|
|
4317
|
+
let content = fs21.readFileSync(indexPath, "utf8");
|
|
4249
4318
|
const row = `| ${slug} | ${query} | ${createdAt} |`;
|
|
4250
4319
|
if (content.includes("## Topics")) {
|
|
4251
4320
|
const topicsIdx = content.indexOf("## Topics");
|
|
@@ -4268,13 +4337,13 @@ ${row}
|
|
|
4268
4337
|
${row}
|
|
4269
4338
|
`;
|
|
4270
4339
|
}
|
|
4271
|
-
|
|
4340
|
+
fs21.writeFileSync(indexPath, content, "utf8");
|
|
4272
4341
|
}
|
|
4273
4342
|
|
|
4274
4343
|
// src/commands/wiki-audit-status.ts
|
|
4275
4344
|
init_cjs_shims();
|
|
4276
|
-
var
|
|
4277
|
-
var
|
|
4345
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
4346
|
+
var path24 = __toESM(require("path"), 1);
|
|
4278
4347
|
var readline4 = __toESM(require("readline"), 1);
|
|
4279
4348
|
var TERMINAL = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
|
|
4280
4349
|
async function wikiAuditStatusHandler(opts = {}) {
|
|
@@ -4287,8 +4356,8 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
4287
4356
|
});
|
|
4288
4357
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
4289
4358
|
const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
|
|
4290
|
-
const deliveryRoot =
|
|
4291
|
-
if (!
|
|
4359
|
+
const deliveryRoot = path24.join(cwd, ".cleargate", "delivery");
|
|
4360
|
+
if (!fs22.existsSync(deliveryRoot)) {
|
|
4292
4361
|
stderr(`audit-status: .cleargate/delivery/ not found at ${deliveryRoot}
|
|
4293
4362
|
`);
|
|
4294
4363
|
exit(1);
|
|
@@ -4428,7 +4497,7 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
4428
4497
|
}
|
|
4429
4498
|
}
|
|
4430
4499
|
for (const d of fixable) {
|
|
4431
|
-
const rawText =
|
|
4500
|
+
const rawText = fs22.readFileSync(d.absPath, "utf8");
|
|
4432
4501
|
const updated = applyStatusFix(rawText, d.suggestedStatus);
|
|
4433
4502
|
if (!opts.quiet) {
|
|
4434
4503
|
stdout(`--- ${d.rawPath}
|
|
@@ -4444,7 +4513,7 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
4444
4513
|
stdout(`+${newLine}
|
|
4445
4514
|
`);
|
|
4446
4515
|
}
|
|
4447
|
-
|
|
4516
|
+
fs22.writeFileSync(d.absPath, updated, "utf8");
|
|
4448
4517
|
}
|
|
4449
4518
|
stdout(`audit-status: applied ${fixable.length} fix(es)
|
|
4450
4519
|
`);
|
|
@@ -4473,8 +4542,8 @@ function applyStatusFix(rawText, newStatus) {
|
|
|
4473
4542
|
|
|
4474
4543
|
// src/commands/doctor.ts
|
|
4475
4544
|
init_cjs_shims();
|
|
4476
|
-
var
|
|
4477
|
-
var
|
|
4545
|
+
var fs23 = __toESM(require("fs"), 1);
|
|
4546
|
+
var path25 = __toESM(require("path"), 1);
|
|
4478
4547
|
var import_node_child_process6 = require("child_process");
|
|
4479
4548
|
|
|
4480
4549
|
// src/lib/pricing.ts
|
|
@@ -4551,24 +4620,24 @@ function parseHookLogLine(line) {
|
|
|
4551
4620
|
};
|
|
4552
4621
|
}
|
|
4553
4622
|
function runHookHealth(stdout, cwd, now, outcome) {
|
|
4554
|
-
const cleargateDir =
|
|
4555
|
-
if (!
|
|
4623
|
+
const cleargateDir = path25.join(cwd, ".cleargate");
|
|
4624
|
+
if (!fs23.existsSync(cleargateDir)) {
|
|
4556
4625
|
stdout("cleargate misconfigured: no .cleargate/ found. Run: cleargate init");
|
|
4557
4626
|
if (outcome) outcome.configError = true;
|
|
4558
4627
|
return;
|
|
4559
4628
|
}
|
|
4560
|
-
const manifestPath =
|
|
4561
|
-
if (!
|
|
4629
|
+
const manifestPath = path25.join(cwd, "cleargate-planning", "MANIFEST.json");
|
|
4630
|
+
if (!fs23.existsSync(manifestPath)) {
|
|
4562
4631
|
stdout(`cleargate misconfigured: cleargate-planning/MANIFEST.json not found. Run: cleargate init`);
|
|
4563
4632
|
if (outcome) outcome.configError = true;
|
|
4564
4633
|
}
|
|
4565
|
-
const settingsPath =
|
|
4566
|
-
if (!
|
|
4634
|
+
const settingsPath = path25.join(cwd, ".claude", "settings.json");
|
|
4635
|
+
if (!fs23.existsSync(settingsPath)) {
|
|
4567
4636
|
stdout("[doctor] No .claude/settings.json found \u2014 hook config unavailable.");
|
|
4568
4637
|
return;
|
|
4569
4638
|
}
|
|
4570
4639
|
try {
|
|
4571
|
-
const raw =
|
|
4640
|
+
const raw = fs23.readFileSync(settingsPath, "utf-8");
|
|
4572
4641
|
const settings = JSON.parse(raw);
|
|
4573
4642
|
const hasHooks = typeof settings === "object" && settings !== null && "hooks" in settings;
|
|
4574
4643
|
if (hasHooks) {
|
|
@@ -4579,13 +4648,13 @@ function runHookHealth(stdout, cwd, now, outcome) {
|
|
|
4579
4648
|
} catch {
|
|
4580
4649
|
stdout("[doctor] .claude/settings.json is not valid JSON \u2014 cannot verify hook config.");
|
|
4581
4650
|
}
|
|
4582
|
-
const logPath =
|
|
4583
|
-
if (!
|
|
4651
|
+
const logPath = path25.join(cwd, ".cleargate", "hook-log", "gate-check.log");
|
|
4652
|
+
if (!fs23.existsSync(logPath)) {
|
|
4584
4653
|
return;
|
|
4585
4654
|
}
|
|
4586
4655
|
let logContent;
|
|
4587
4656
|
try {
|
|
4588
|
-
logContent =
|
|
4657
|
+
logContent = fs23.readFileSync(logPath, "utf-8");
|
|
4589
4658
|
} catch {
|
|
4590
4659
|
return;
|
|
4591
4660
|
}
|
|
@@ -4699,8 +4768,8 @@ function parseCachedGateResult2(raw) {
|
|
|
4699
4768
|
};
|
|
4700
4769
|
}
|
|
4701
4770
|
function emitResolverStatusLine(cwd, stdout) {
|
|
4702
|
-
const distCliPath =
|
|
4703
|
-
if (
|
|
4771
|
+
const distCliPath = path25.join(cwd, "cleargate-cli", "dist", "cli.js");
|
|
4772
|
+
if (fs23.existsSync(distCliPath)) {
|
|
4704
4773
|
stdout(`cleargate CLI: local dist \u2014 ${distCliPath}`);
|
|
4705
4774
|
return;
|
|
4706
4775
|
}
|
|
@@ -4714,10 +4783,10 @@ function emitResolverStatusLine(cwd, stdout) {
|
|
|
4714
4783
|
return;
|
|
4715
4784
|
}
|
|
4716
4785
|
let pinVersion = "unknown";
|
|
4717
|
-
const hookPath =
|
|
4718
|
-
if (
|
|
4786
|
+
const hookPath = path25.join(cwd, ".claude", "hooks", "stamp-and-gate.sh");
|
|
4787
|
+
if (fs23.existsSync(hookPath)) {
|
|
4719
4788
|
try {
|
|
4720
|
-
const hookContent =
|
|
4789
|
+
const hookContent = fs23.readFileSync(hookPath, "utf-8");
|
|
4721
4790
|
const pinMatch = hookContent.match(/^#\s*cleargate-pin:\s*(\S+)\s*$/m);
|
|
4722
4791
|
if (pinMatch?.[1]) {
|
|
4723
4792
|
pinVersion = pinMatch[1];
|
|
@@ -4749,10 +4818,10 @@ async function runSessionStart(cwd, stdout, outcome) {
|
|
|
4749
4818
|
if (outcome && resolverLines.some((l) => l.includes("\u{1F534}"))) {
|
|
4750
4819
|
outcome.configError = true;
|
|
4751
4820
|
}
|
|
4752
|
-
const pendingSyncDir =
|
|
4821
|
+
const pendingSyncDir = path25.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
4753
4822
|
let files;
|
|
4754
4823
|
try {
|
|
4755
|
-
files =
|
|
4824
|
+
files = fs23.readdirSync(pendingSyncDir).filter((f) => f.endsWith(".md")).map((f) => path25.join(pendingSyncDir, f));
|
|
4756
4825
|
} catch {
|
|
4757
4826
|
return;
|
|
4758
4827
|
}
|
|
@@ -4761,7 +4830,7 @@ async function runSessionStart(cwd, stdout, outcome) {
|
|
|
4761
4830
|
for (const filePath of files) {
|
|
4762
4831
|
let raw;
|
|
4763
4832
|
try {
|
|
4764
|
-
raw =
|
|
4833
|
+
raw = fs23.readFileSync(filePath, "utf-8");
|
|
4765
4834
|
} catch {
|
|
4766
4835
|
continue;
|
|
4767
4836
|
}
|
|
@@ -4787,13 +4856,13 @@ async function runSessionStart(cwd, stdout, outcome) {
|
|
|
4787
4856
|
}
|
|
4788
4857
|
}
|
|
4789
4858
|
if (!itemId) {
|
|
4790
|
-
itemId =
|
|
4859
|
+
itemId = path25.basename(filePath, ".md");
|
|
4791
4860
|
}
|
|
4792
4861
|
const firstCriterionId = gate2.failing_criteria.length > 0 ? gate2.failing_criteria[0]?.id ?? "" : "";
|
|
4793
4862
|
blocked.push({ id: itemId, firstCriterionId });
|
|
4794
4863
|
}
|
|
4795
|
-
const activesentinel =
|
|
4796
|
-
const sprintActive =
|
|
4864
|
+
const activesentinel = path25.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
4865
|
+
const sprintActive = fs23.existsSync(activesentinel);
|
|
4797
4866
|
const shouldRemind = !hasApprovedStory && !sprintActive;
|
|
4798
4867
|
if (shouldRemind) {
|
|
4799
4868
|
stdout(PLANNING_FIRST_REMINDER);
|
|
@@ -4828,10 +4897,10 @@ async function runPricing(filePath, cwd, stdout, stderr, exit, outcome) {
|
|
|
4828
4897
|
exit(2);
|
|
4829
4898
|
return;
|
|
4830
4899
|
}
|
|
4831
|
-
const absPath =
|
|
4900
|
+
const absPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(cwd, filePath);
|
|
4832
4901
|
let raw;
|
|
4833
4902
|
try {
|
|
4834
|
-
raw =
|
|
4903
|
+
raw = fs23.readFileSync(absPath, "utf-8");
|
|
4835
4904
|
} catch {
|
|
4836
4905
|
stderr(`cleargate doctor --pricing: cannot read file: ${absPath}`);
|
|
4837
4906
|
if (outcome) outcome.configError = true;
|
|
@@ -4893,7 +4962,7 @@ async function runPricing(filePath, cwd, stdout, stderr, exit, outcome) {
|
|
|
4893
4962
|
const output = draftTokens.output ?? 0;
|
|
4894
4963
|
const cacheRead = draftTokens.cache_read ?? 0;
|
|
4895
4964
|
const cacheCreation = draftTokens.cache_creation ?? 0;
|
|
4896
|
-
const fileName =
|
|
4965
|
+
const fileName = path25.basename(absPath);
|
|
4897
4966
|
stdout(
|
|
4898
4967
|
`${fileName}: ${model} \u2014 input:${input} output:${output} cache_read:${cacheRead} cache_creation:${cacheCreation} \u2248 $${usd.toFixed(4)}`
|
|
4899
4968
|
);
|
|
@@ -4906,15 +4975,15 @@ function globMatch(pattern, filePath) {
|
|
|
4906
4975
|
return re.test(normalFile);
|
|
4907
4976
|
}
|
|
4908
4977
|
async function runCanEdit(filePath, cwd, stdout, exit, outcome) {
|
|
4909
|
-
const activeSentinel =
|
|
4910
|
-
if (
|
|
4978
|
+
const activeSentinel = path25.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
4979
|
+
if (fs23.existsSync(activeSentinel)) {
|
|
4911
4980
|
stdout("allowed: sprint active");
|
|
4912
4981
|
return;
|
|
4913
4982
|
}
|
|
4914
|
-
const pendingSyncDir =
|
|
4983
|
+
const pendingSyncDir = path25.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
4915
4984
|
let files;
|
|
4916
4985
|
try {
|
|
4917
|
-
files =
|
|
4986
|
+
files = fs23.readdirSync(pendingSyncDir).filter((f) => f.endsWith(".md")).map((f) => path25.join(pendingSyncDir, f));
|
|
4918
4987
|
} catch {
|
|
4919
4988
|
stdout("blocked: no_approved_stories");
|
|
4920
4989
|
if (outcome) outcome.blocker = true;
|
|
@@ -4926,7 +4995,7 @@ async function runCanEdit(filePath, cwd, stdout, exit, outcome) {
|
|
|
4926
4995
|
for (const storyPath of files) {
|
|
4927
4996
|
let raw;
|
|
4928
4997
|
try {
|
|
4929
|
-
raw =
|
|
4998
|
+
raw = fs23.readFileSync(storyPath, "utf-8");
|
|
4930
4999
|
} catch {
|
|
4931
5000
|
continue;
|
|
4932
5001
|
}
|
|
@@ -5024,14 +5093,14 @@ async function doctorHandler(flags, cli) {
|
|
|
5024
5093
|
|
|
5025
5094
|
// src/commands/gate.ts
|
|
5026
5095
|
init_cjs_shims();
|
|
5027
|
-
var
|
|
5028
|
-
var
|
|
5096
|
+
var fs27 = __toESM(require("fs"), 1);
|
|
5097
|
+
var path28 = __toESM(require("path"), 1);
|
|
5029
5098
|
var import_node_child_process7 = require("child_process");
|
|
5030
5099
|
|
|
5031
5100
|
// src/commands/execution-mode.ts
|
|
5032
5101
|
init_cjs_shims();
|
|
5033
|
-
var
|
|
5034
|
-
var
|
|
5102
|
+
var fs24 = __toESM(require("fs"), 1);
|
|
5103
|
+
var path26 = __toESM(require("path"), 1);
|
|
5035
5104
|
var V1_INERT_MESSAGE = "v1 mode active \u2014 command inert. Set execution_mode: v2 in sprint frontmatter to enable.";
|
|
5036
5105
|
function parseFrontmatterSimple(raw) {
|
|
5037
5106
|
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
@@ -5049,24 +5118,24 @@ function parseFrontmatterSimple(raw) {
|
|
|
5049
5118
|
}
|
|
5050
5119
|
function discoverSprintFile(sprintId, cwd) {
|
|
5051
5120
|
const searchDirs = [
|
|
5052
|
-
|
|
5053
|
-
|
|
5121
|
+
path26.join(cwd, ".cleargate", "delivery", "pending-sync"),
|
|
5122
|
+
path26.join(cwd, ".cleargate", "delivery", "archive")
|
|
5054
5123
|
];
|
|
5055
5124
|
for (const dir of searchDirs) {
|
|
5056
|
-
if (!
|
|
5125
|
+
if (!fs24.existsSync(dir)) continue;
|
|
5057
5126
|
let entries;
|
|
5058
5127
|
try {
|
|
5059
|
-
entries =
|
|
5128
|
+
entries = fs24.readdirSync(dir);
|
|
5060
5129
|
} catch {
|
|
5061
5130
|
continue;
|
|
5062
5131
|
}
|
|
5063
5132
|
const prefix = `${sprintId}_`;
|
|
5064
5133
|
for (const entry of entries) {
|
|
5065
5134
|
if (entry.startsWith(prefix) && entry.endsWith(".md")) {
|
|
5066
|
-
return
|
|
5135
|
+
return path26.join(dir, entry);
|
|
5067
5136
|
}
|
|
5068
5137
|
if (entry === `${sprintId}.md`) {
|
|
5069
|
-
return
|
|
5138
|
+
return path26.join(dir, entry);
|
|
5070
5139
|
}
|
|
5071
5140
|
}
|
|
5072
5141
|
}
|
|
@@ -5074,9 +5143,9 @@ function discoverSprintFile(sprintId, cwd) {
|
|
|
5074
5143
|
}
|
|
5075
5144
|
function resolveSprintIdFromSentinel(cwd) {
|
|
5076
5145
|
const resolvedCwd = cwd ?? process.cwd();
|
|
5077
|
-
const sentinelPath =
|
|
5146
|
+
const sentinelPath = path26.join(resolvedCwd, ".cleargate", "sprint-runs", ".active");
|
|
5078
5147
|
try {
|
|
5079
|
-
const content =
|
|
5148
|
+
const content = fs24.readFileSync(sentinelPath, "utf8").trim();
|
|
5080
5149
|
return content.length > 0 ? content : null;
|
|
5081
5150
|
} catch {
|
|
5082
5151
|
return null;
|
|
@@ -5095,12 +5164,12 @@ function readSprintExecutionMode(sprintId, opts = {}) {
|
|
|
5095
5164
|
if (!filePath) {
|
|
5096
5165
|
filePath = discoverSprintFile(resolvedSprintId, cwd);
|
|
5097
5166
|
}
|
|
5098
|
-
if (!filePath || !
|
|
5167
|
+
if (!filePath || !fs24.existsSync(filePath)) {
|
|
5099
5168
|
return "v1";
|
|
5100
5169
|
}
|
|
5101
5170
|
let raw;
|
|
5102
5171
|
try {
|
|
5103
|
-
raw =
|
|
5172
|
+
raw = fs24.readFileSync(filePath, "utf8");
|
|
5104
5173
|
} catch {
|
|
5105
5174
|
return "v1";
|
|
5106
5175
|
}
|
|
@@ -5119,8 +5188,8 @@ var import_js_yaml6 = __toESM(require("js-yaml"), 1);
|
|
|
5119
5188
|
|
|
5120
5189
|
// src/lib/readiness-predicates.ts
|
|
5121
5190
|
init_cjs_shims();
|
|
5122
|
-
var
|
|
5123
|
-
var
|
|
5191
|
+
var fs25 = __toESM(require("fs"), 1);
|
|
5192
|
+
var path27 = __toESM(require("path"), 1);
|
|
5124
5193
|
function parsePredicate(src) {
|
|
5125
5194
|
const s = src.trim();
|
|
5126
5195
|
const fmMatch = s.match(
|
|
@@ -5284,18 +5353,18 @@ function compareValues(actual, op, expected) {
|
|
|
5284
5353
|
}
|
|
5285
5354
|
function resolveLinkedPath(ref, docAbsPath, projectRoot) {
|
|
5286
5355
|
const candidates = [
|
|
5287
|
-
|
|
5288
|
-
|
|
5356
|
+
path27.resolve(path27.dirname(docAbsPath), ref),
|
|
5357
|
+
path27.resolve(projectRoot, ref)
|
|
5289
5358
|
];
|
|
5290
5359
|
for (const candidate of candidates) {
|
|
5291
5360
|
if (!candidate.startsWith(projectRoot)) continue;
|
|
5292
|
-
if (
|
|
5361
|
+
if (fs25.existsSync(candidate)) return candidate;
|
|
5293
5362
|
}
|
|
5294
5363
|
return null;
|
|
5295
5364
|
}
|
|
5296
5365
|
function readFrontmatterFromFile(absPath) {
|
|
5297
5366
|
try {
|
|
5298
|
-
const raw =
|
|
5367
|
+
const raw = fs25.readFileSync(absPath, "utf8");
|
|
5299
5368
|
const lines = raw.split("\n");
|
|
5300
5369
|
if (lines[0] !== "---") return {};
|
|
5301
5370
|
let closeIdx = -1;
|
|
@@ -5439,14 +5508,14 @@ function applyCountOp(actual, op, n) {
|
|
|
5439
5508
|
}
|
|
5440
5509
|
}
|
|
5441
5510
|
function evalFileExists(parsed, projectRoot) {
|
|
5442
|
-
const resolved =
|
|
5443
|
-
if (!resolved.startsWith(projectRoot +
|
|
5511
|
+
const resolved = path27.resolve(projectRoot, parsed.path);
|
|
5512
|
+
if (!resolved.startsWith(projectRoot + path27.sep) && resolved !== projectRoot) {
|
|
5444
5513
|
return {
|
|
5445
5514
|
pass: false,
|
|
5446
5515
|
detail: `path '${parsed.path}' resolves outside project root (sandbox violation)`
|
|
5447
5516
|
};
|
|
5448
5517
|
}
|
|
5449
|
-
const exists =
|
|
5518
|
+
const exists = fs25.existsSync(resolved);
|
|
5450
5519
|
return {
|
|
5451
5520
|
pass: exists,
|
|
5452
5521
|
detail: exists ? `${parsed.path} exists` : `${parsed.path} not found`
|
|
@@ -5454,13 +5523,13 @@ function evalFileExists(parsed, projectRoot) {
|
|
|
5454
5523
|
}
|
|
5455
5524
|
function evalLinkTargetExists(parsed, opts) {
|
|
5456
5525
|
const projectRoot = opts?.projectRoot ?? process.cwd();
|
|
5457
|
-
const wikiIndexPath = opts?.wikiIndexPath ??
|
|
5526
|
+
const wikiIndexPath = opts?.wikiIndexPath ?? path27.join(projectRoot, ".cleargate", "wiki", "index.md");
|
|
5458
5527
|
if (!wikiIndexPath.startsWith(projectRoot)) {
|
|
5459
5528
|
return { pass: false, detail: "wikiIndexPath resolves outside project root" };
|
|
5460
5529
|
}
|
|
5461
5530
|
let indexContent;
|
|
5462
5531
|
try {
|
|
5463
|
-
indexContent =
|
|
5532
|
+
indexContent = fs25.readFileSync(wikiIndexPath, "utf8");
|
|
5464
5533
|
} catch {
|
|
5465
5534
|
return { pass: false, detail: `wiki index not found at ${wikiIndexPath}` };
|
|
5466
5535
|
}
|
|
@@ -5471,13 +5540,13 @@ function evalLinkTargetExists(parsed, opts) {
|
|
|
5471
5540
|
};
|
|
5472
5541
|
}
|
|
5473
5542
|
function evalStatusOf(parsed, opts, projectRoot) {
|
|
5474
|
-
const wikiIndexPath = opts?.wikiIndexPath ??
|
|
5543
|
+
const wikiIndexPath = opts?.wikiIndexPath ?? path27.join(projectRoot, ".cleargate", "wiki", "index.md");
|
|
5475
5544
|
if (!wikiIndexPath.startsWith(projectRoot)) {
|
|
5476
5545
|
return { pass: false, detail: "wikiIndexPath resolves outside project root" };
|
|
5477
5546
|
}
|
|
5478
5547
|
let indexContent;
|
|
5479
5548
|
try {
|
|
5480
|
-
indexContent =
|
|
5549
|
+
indexContent = fs25.readFileSync(wikiIndexPath, "utf8");
|
|
5481
5550
|
} catch {
|
|
5482
5551
|
return { pass: false, detail: `wiki index not found at ${wikiIndexPath}` };
|
|
5483
5552
|
}
|
|
@@ -5488,7 +5557,7 @@ function evalStatusOf(parsed, opts, projectRoot) {
|
|
|
5488
5557
|
return { pass: false, detail: `[[${parsed.id}]] not found in wiki index` };
|
|
5489
5558
|
}
|
|
5490
5559
|
const rawPath = rowMatch[1].trim();
|
|
5491
|
-
const fullPath =
|
|
5560
|
+
const fullPath = path27.resolve(projectRoot, rawPath);
|
|
5492
5561
|
if (!fullPath.startsWith(projectRoot)) {
|
|
5493
5562
|
return { pass: false, detail: `wiki path for ${parsed.id} resolves outside project root` };
|
|
5494
5563
|
}
|
|
@@ -5506,12 +5575,12 @@ function evalStatusOf(parsed, opts, projectRoot) {
|
|
|
5506
5575
|
|
|
5507
5576
|
// src/lib/frontmatter-cache.ts
|
|
5508
5577
|
init_cjs_shims();
|
|
5509
|
-
var
|
|
5578
|
+
var fs26 = __toESM(require("fs/promises"), 1);
|
|
5510
5579
|
var import_js_yaml5 = __toESM(require("js-yaml"), 1);
|
|
5511
5580
|
async function readCachedGate(absPath) {
|
|
5512
5581
|
let raw;
|
|
5513
5582
|
try {
|
|
5514
|
-
raw = await
|
|
5583
|
+
raw = await fs26.readFile(absPath, "utf8");
|
|
5515
5584
|
} catch {
|
|
5516
5585
|
return null;
|
|
5517
5586
|
}
|
|
@@ -5531,7 +5600,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
5531
5600
|
failing_criteria: result.failing_criteria,
|
|
5532
5601
|
last_gate_check: lastGateCheck
|
|
5533
5602
|
};
|
|
5534
|
-
const raw = await
|
|
5603
|
+
const raw = await fs26.readFile(absPath, "utf8");
|
|
5535
5604
|
let fm;
|
|
5536
5605
|
let body;
|
|
5537
5606
|
try {
|
|
@@ -5561,7 +5630,7 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
5561
5630
|
|
|
5562
5631
|
${body}` : `${fmBlock}
|
|
5563
5632
|
`;
|
|
5564
|
-
await
|
|
5633
|
+
await fs26.writeFile(absPath, newContent, "utf8");
|
|
5565
5634
|
}
|
|
5566
5635
|
function coerceCachedGate(val) {
|
|
5567
5636
|
if (val === void 0 || val === null) return null;
|
|
@@ -5592,7 +5661,7 @@ function coerceCachedGate(val) {
|
|
|
5592
5661
|
|
|
5593
5662
|
// src/commands/gate.ts
|
|
5594
5663
|
function loadGateBlocks(gatesDocPath) {
|
|
5595
|
-
const raw =
|
|
5664
|
+
const raw = fs27.readFileSync(gatesDocPath, "utf8");
|
|
5596
5665
|
const blocks = [];
|
|
5597
5666
|
const fenceRe = /^```yaml\n([\s\S]*?)^```/gm;
|
|
5598
5667
|
let match;
|
|
@@ -5627,14 +5696,14 @@ async function gateCheckHandler(file, opts, cli) {
|
|
|
5627
5696
|
const exitFn = cli?.exit ?? ((code) => process.exit(code));
|
|
5628
5697
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5629
5698
|
const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
|
|
5630
|
-
const absPath =
|
|
5631
|
-
if (!
|
|
5699
|
+
const absPath = path28.isAbsolute(file) ? file : path28.resolve(cwd, file);
|
|
5700
|
+
if (!fs27.existsSync(absPath)) {
|
|
5632
5701
|
stderrFn(`[cleargate gate] error: file not found: ${absPath}`);
|
|
5633
5702
|
return exitFn(1);
|
|
5634
5703
|
}
|
|
5635
5704
|
let raw;
|
|
5636
5705
|
try {
|
|
5637
|
-
raw =
|
|
5706
|
+
raw = fs27.readFileSync(absPath, "utf8");
|
|
5638
5707
|
} catch (err) {
|
|
5639
5708
|
stderrFn(`[cleargate gate] error: cannot read file: ${absPath}`);
|
|
5640
5709
|
return exitFn(1);
|
|
@@ -5653,8 +5722,8 @@ async function gateCheckHandler(file, opts, cli) {
|
|
|
5653
5722
|
return exitFn(1);
|
|
5654
5723
|
}
|
|
5655
5724
|
const projectRoot = cwd;
|
|
5656
|
-
const gatesDocPath = cli?.gatesDocPath ??
|
|
5657
|
-
if (!
|
|
5725
|
+
const gatesDocPath = cli?.gatesDocPath ?? path28.join(projectRoot, ".cleargate", "knowledge", "readiness-gates.md");
|
|
5726
|
+
if (!fs27.existsSync(gatesDocPath)) {
|
|
5658
5727
|
stderrFn(`[cleargate gate] error: readiness-gates.md not found at: ${gatesDocPath}`);
|
|
5659
5728
|
return exitFn(1);
|
|
5660
5729
|
}
|
|
@@ -5727,8 +5796,8 @@ async function gateExplainHandler(file, cli) {
|
|
|
5727
5796
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5728
5797
|
const exitFn = cli?.exit ?? ((code) => process.exit(code));
|
|
5729
5798
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5730
|
-
const absPath =
|
|
5731
|
-
if (!
|
|
5799
|
+
const absPath = path28.isAbsolute(file) ? file : path28.resolve(cwd, file);
|
|
5800
|
+
if (!fs27.existsSync(absPath)) {
|
|
5732
5801
|
stderrFn(`[cleargate gate] error: file not found: ${absPath}`);
|
|
5733
5802
|
return exitFn(1);
|
|
5734
5803
|
}
|
|
@@ -5739,7 +5808,7 @@ async function gateExplainHandler(file, cli) {
|
|
|
5739
5808
|
}
|
|
5740
5809
|
let raw;
|
|
5741
5810
|
try {
|
|
5742
|
-
raw =
|
|
5811
|
+
raw = fs27.readFileSync(absPath, "utf8");
|
|
5743
5812
|
} catch {
|
|
5744
5813
|
stderrFn(`[cleargate gate] error: cannot read file: ${absPath}`);
|
|
5745
5814
|
return exitFn(1);
|
|
@@ -5760,7 +5829,7 @@ async function gateExplainHandler(file, cli) {
|
|
|
5760
5829
|
function resolveRunScriptForGate(opts) {
|
|
5761
5830
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
5762
5831
|
const cwd = opts.cwd ?? process.cwd();
|
|
5763
|
-
return
|
|
5832
|
+
return path28.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
5764
5833
|
}
|
|
5765
5834
|
function gateQaHandler(opts, cli) {
|
|
5766
5835
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -5854,15 +5923,15 @@ function gateRunHandler(name, opts, cli) {
|
|
|
5854
5923
|
|
|
5855
5924
|
// src/commands/sprint.ts
|
|
5856
5925
|
init_cjs_shims();
|
|
5857
|
-
var
|
|
5858
|
-
var
|
|
5926
|
+
var fs28 = __toESM(require("fs"), 1);
|
|
5927
|
+
var path29 = __toESM(require("path"), 1);
|
|
5859
5928
|
var import_node_child_process9 = require("child_process");
|
|
5860
5929
|
var import_js_yaml7 = __toESM(require("js-yaml"), 1);
|
|
5861
5930
|
var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
|
|
5862
5931
|
function resolveRunScript(opts) {
|
|
5863
5932
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
5864
5933
|
const cwd = opts.cwd ?? process.cwd();
|
|
5865
|
-
return
|
|
5934
|
+
return path29.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
5866
5935
|
}
|
|
5867
5936
|
function defaultExit(code) {
|
|
5868
5937
|
return process.exit(code);
|
|
@@ -5956,8 +6025,8 @@ ${body}`;
|
|
|
5956
6025
|
}
|
|
5957
6026
|
function atomicWriteStr(filePath, content) {
|
|
5958
6027
|
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
5959
|
-
|
|
5960
|
-
|
|
6028
|
+
fs28.writeFileSync(tmp, content, "utf8");
|
|
6029
|
+
fs28.renameSync(tmp, filePath);
|
|
5961
6030
|
}
|
|
5962
6031
|
function deriveSprintBranchForArchive(sprintId) {
|
|
5963
6032
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -5971,7 +6040,7 @@ function stampFile(raw, status, completedAt) {
|
|
|
5971
6040
|
return serializeFileContent(fm, body);
|
|
5972
6041
|
}
|
|
5973
6042
|
function stampSprintClose(sprintPath, now) {
|
|
5974
|
-
const previousContent =
|
|
6043
|
+
const previousContent = fs28.readFileSync(sprintPath, "utf8");
|
|
5975
6044
|
const { fm, body } = parseFileFrontmatter(previousContent);
|
|
5976
6045
|
const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
|
|
5977
6046
|
const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
|
|
@@ -6031,14 +6100,14 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6031
6100
|
if (mode === "v1") {
|
|
6032
6101
|
return printInertAndExit(stdoutFn, exitFn);
|
|
6033
6102
|
}
|
|
6034
|
-
const stateFile =
|
|
6035
|
-
if (!
|
|
6103
|
+
const stateFile = path29.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
|
|
6104
|
+
if (!fs28.existsSync(stateFile)) {
|
|
6036
6105
|
stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
|
|
6037
6106
|
return exitFn(1);
|
|
6038
6107
|
}
|
|
6039
6108
|
let state2;
|
|
6040
6109
|
try {
|
|
6041
|
-
state2 = JSON.parse(
|
|
6110
|
+
state2 = JSON.parse(fs28.readFileSync(stateFile, "utf8"));
|
|
6042
6111
|
} catch (err) {
|
|
6043
6112
|
stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
|
|
6044
6113
|
return exitFn(1);
|
|
@@ -6050,18 +6119,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6050
6119
|
return exitFn(1);
|
|
6051
6120
|
}
|
|
6052
6121
|
const stateStories = state2.stories ?? {};
|
|
6053
|
-
const pendingDir =
|
|
6054
|
-
const archiveDir =
|
|
6122
|
+
const pendingDir = path29.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
6123
|
+
const archiveDir = path29.join(cwd, ".cleargate", "delivery", "archive");
|
|
6055
6124
|
let sprintFile = null;
|
|
6056
|
-
for (const entry of
|
|
6125
|
+
for (const entry of fs28.readdirSync(pendingDir)) {
|
|
6057
6126
|
if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
|
|
6058
|
-
sprintFile =
|
|
6127
|
+
sprintFile = path29.join(pendingDir, entry);
|
|
6059
6128
|
break;
|
|
6060
6129
|
}
|
|
6061
6130
|
}
|
|
6062
6131
|
let epicIds = [];
|
|
6063
|
-
if (sprintFile &&
|
|
6064
|
-
const { fm } = parseFileFrontmatter(
|
|
6132
|
+
if (sprintFile && fs28.existsSync(sprintFile)) {
|
|
6133
|
+
const { fm } = parseFileFrontmatter(fs28.readFileSync(sprintFile, "utf8"));
|
|
6065
6134
|
const epics = fm["epics"];
|
|
6066
6135
|
if (Array.isArray(epics)) {
|
|
6067
6136
|
epicIds = epics.map(String);
|
|
@@ -6071,15 +6140,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6071
6140
|
if (sprintFile) {
|
|
6072
6141
|
plan.push({
|
|
6073
6142
|
src: sprintFile,
|
|
6074
|
-
destName:
|
|
6143
|
+
destName: path29.basename(sprintFile),
|
|
6075
6144
|
status: "Completed"
|
|
6076
6145
|
});
|
|
6077
6146
|
}
|
|
6078
6147
|
for (const epicId of epicIds) {
|
|
6079
|
-
for (const entry of
|
|
6148
|
+
for (const entry of fs28.readdirSync(pendingDir)) {
|
|
6080
6149
|
if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
|
|
6081
6150
|
plan.push({
|
|
6082
|
-
src:
|
|
6151
|
+
src: path29.join(pendingDir, entry),
|
|
6083
6152
|
destName: entry,
|
|
6084
6153
|
status: "Approved"
|
|
6085
6154
|
});
|
|
@@ -6088,10 +6157,10 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6088
6157
|
}
|
|
6089
6158
|
const storyKeys = storyKeysForEpic(stateStories, epicId);
|
|
6090
6159
|
for (const storyId of storyKeys) {
|
|
6091
|
-
for (const entry of
|
|
6160
|
+
for (const entry of fs28.readdirSync(pendingDir)) {
|
|
6092
6161
|
if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
|
|
6093
6162
|
plan.push({
|
|
6094
|
-
src:
|
|
6163
|
+
src: path29.join(pendingDir, entry),
|
|
6095
6164
|
destName: entry,
|
|
6096
6165
|
status: "Done"
|
|
6097
6166
|
});
|
|
@@ -6103,13 +6172,13 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6103
6172
|
const storyIdsInState = new Set(Object.keys(stateStories));
|
|
6104
6173
|
const planSrcs = new Set(plan.map((p) => p.src));
|
|
6105
6174
|
const orphans = [];
|
|
6106
|
-
for (const entry of
|
|
6175
|
+
for (const entry of fs28.readdirSync(pendingDir)) {
|
|
6107
6176
|
if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
|
|
6108
|
-
const candidate =
|
|
6177
|
+
const candidate = path29.join(pendingDir, entry);
|
|
6109
6178
|
if (planSrcs.has(candidate)) continue;
|
|
6110
6179
|
let raw;
|
|
6111
6180
|
try {
|
|
6112
|
-
raw =
|
|
6181
|
+
raw = fs28.readFileSync(candidate, "utf8");
|
|
6113
6182
|
} catch {
|
|
6114
6183
|
continue;
|
|
6115
6184
|
}
|
|
@@ -6126,18 +6195,18 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6126
6195
|
}
|
|
6127
6196
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6128
6197
|
const sprintBranch = deriveSprintBranchForArchive(opts.sprintId);
|
|
6129
|
-
const activePath =
|
|
6198
|
+
const activePath = path29.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
6130
6199
|
if (opts.dryRun) {
|
|
6131
6200
|
stdoutFn(`[dry-run] Sprint archive plan for ${opts.sprintId}:`);
|
|
6132
6201
|
stdoutFn(` Sprint branch: ${sprintBranch}`);
|
|
6133
6202
|
stdoutFn(` Files to archive (${plan.length}):`);
|
|
6134
6203
|
for (const entry of plan) {
|
|
6135
6204
|
stdoutFn(
|
|
6136
|
-
` ${
|
|
6205
|
+
` ${path29.basename(entry.src)} \u2192 archive/${entry.destName} [stamp: status=${entry.status}, completed_at=<now>]`
|
|
6137
6206
|
);
|
|
6138
6207
|
}
|
|
6139
6208
|
if (orphans.length > 0) {
|
|
6140
|
-
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) =>
|
|
6209
|
+
stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) => path29.basename(o)).join(", ")}`);
|
|
6141
6210
|
}
|
|
6142
6211
|
stdoutFn(` .active \u2192 "" (truncate)`);
|
|
6143
6212
|
stdoutFn(` git checkout main`);
|
|
@@ -6146,9 +6215,9 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6146
6215
|
return exitFn(0);
|
|
6147
6216
|
}
|
|
6148
6217
|
let sprintFileSnapshot = null;
|
|
6149
|
-
const wikiRoot =
|
|
6150
|
-
const wikiInitialised =
|
|
6151
|
-
if (sprintFile &&
|
|
6218
|
+
const wikiRoot = path29.join(cwd, ".cleargate", "wiki");
|
|
6219
|
+
const wikiInitialised = fs28.existsSync(wikiRoot);
|
|
6220
|
+
if (sprintFile && fs28.existsSync(sprintFile)) {
|
|
6152
6221
|
const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
|
|
6153
6222
|
sprintFileSnapshot = previousContent;
|
|
6154
6223
|
if (wikiInitialised) {
|
|
@@ -6170,15 +6239,15 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6170
6239
|
}
|
|
6171
6240
|
}
|
|
6172
6241
|
for (const entry of plan) {
|
|
6173
|
-
if (!
|
|
6242
|
+
if (!fs28.existsSync(entry.src)) {
|
|
6174
6243
|
stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
|
|
6175
6244
|
continue;
|
|
6176
6245
|
}
|
|
6177
|
-
const raw =
|
|
6246
|
+
const raw = fs28.readFileSync(entry.src, "utf8");
|
|
6178
6247
|
const stamped = stampFile(raw, entry.status, completedAt);
|
|
6179
|
-
const dest =
|
|
6248
|
+
const dest = path29.join(archiveDir, entry.destName);
|
|
6180
6249
|
atomicWriteStr(entry.src, stamped);
|
|
6181
|
-
|
|
6250
|
+
fs28.renameSync(entry.src, dest);
|
|
6182
6251
|
stdoutFn(`archived: ${entry.destName}`);
|
|
6183
6252
|
}
|
|
6184
6253
|
try {
|
|
@@ -6221,8 +6290,8 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
6221
6290
|
|
|
6222
6291
|
// src/commands/story.ts
|
|
6223
6292
|
init_cjs_shims();
|
|
6224
|
-
var
|
|
6225
|
-
var
|
|
6293
|
+
var fs29 = __toESM(require("fs"), 1);
|
|
6294
|
+
var path30 = __toESM(require("path"), 1);
|
|
6226
6295
|
var import_node_child_process10 = require("child_process");
|
|
6227
6296
|
function defaultExit2(code) {
|
|
6228
6297
|
return process.exit(code);
|
|
@@ -6230,7 +6299,7 @@ function defaultExit2(code) {
|
|
|
6230
6299
|
function resolveRunScript2(opts) {
|
|
6231
6300
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
6232
6301
|
const cwd = opts.cwd ?? process.cwd();
|
|
6233
|
-
return
|
|
6302
|
+
return path30.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
6234
6303
|
}
|
|
6235
6304
|
function deriveSprintBranch(sprintId) {
|
|
6236
6305
|
const match = /^SPRINT-(\d+)/.exec(sprintId);
|
|
@@ -6239,11 +6308,11 @@ function deriveSprintBranch(sprintId) {
|
|
|
6239
6308
|
}
|
|
6240
6309
|
function atomicWriteString(filePath, text) {
|
|
6241
6310
|
const tmpFile = `${filePath}.tmp.${process.pid}`;
|
|
6242
|
-
|
|
6243
|
-
|
|
6311
|
+
fs29.writeFileSync(tmpFile, text, "utf8");
|
|
6312
|
+
fs29.renameSync(tmpFile, filePath);
|
|
6244
6313
|
}
|
|
6245
6314
|
function stateJsonPath(cwd, sprintId) {
|
|
6246
|
-
return
|
|
6315
|
+
return path30.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
|
|
6247
6316
|
}
|
|
6248
6317
|
function storyStartHandler(opts, cli) {
|
|
6249
6318
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -6260,7 +6329,7 @@ function storyStartHandler(opts, cli) {
|
|
|
6260
6329
|
return printInertAndExit(stdoutFn, exitFn);
|
|
6261
6330
|
}
|
|
6262
6331
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
6263
|
-
const worktreePath =
|
|
6332
|
+
const worktreePath = path30.join(cwd, ".worktrees", opts.storyId);
|
|
6264
6333
|
const storyBranch = `story/${opts.storyId}`;
|
|
6265
6334
|
const step1 = spawnFn(
|
|
6266
6335
|
"git",
|
|
@@ -6292,13 +6361,13 @@ function storyStartHandler(opts, cli) {
|
|
|
6292
6361
|
return exitFn(step2.status ?? 1);
|
|
6293
6362
|
}
|
|
6294
6363
|
const stateFile = stateJsonPath(cwd, sprintId);
|
|
6295
|
-
if (!
|
|
6364
|
+
if (!fs29.existsSync(stateFile)) {
|
|
6296
6365
|
stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
|
|
6297
6366
|
return exitFn(1);
|
|
6298
6367
|
}
|
|
6299
6368
|
let state2;
|
|
6300
6369
|
try {
|
|
6301
|
-
state2 = JSON.parse(
|
|
6370
|
+
state2 = JSON.parse(fs29.readFileSync(stateFile, "utf8"));
|
|
6302
6371
|
} catch (err) {
|
|
6303
6372
|
stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
|
|
6304
6373
|
return exitFn(1);
|
|
@@ -6339,7 +6408,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
6339
6408
|
}
|
|
6340
6409
|
const sprintBranch = deriveSprintBranch(sprintId);
|
|
6341
6410
|
const storyBranch = `story/${opts.storyId}`;
|
|
6342
|
-
const worktreeRel =
|
|
6411
|
+
const worktreeRel = path30.join(".worktrees", opts.storyId);
|
|
6343
6412
|
const step1 = spawnFn(
|
|
6344
6413
|
"git",
|
|
6345
6414
|
["rev-list", "--count", `${sprintBranch}..${storyBranch}`],
|
|
@@ -6437,7 +6506,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
6437
6506
|
|
|
6438
6507
|
// src/commands/state.ts
|
|
6439
6508
|
init_cjs_shims();
|
|
6440
|
-
var
|
|
6509
|
+
var path31 = __toESM(require("path"), 1);
|
|
6441
6510
|
var import_node_child_process11 = require("child_process");
|
|
6442
6511
|
function defaultExit3(code) {
|
|
6443
6512
|
return process.exit(code);
|
|
@@ -6445,7 +6514,7 @@ function defaultExit3(code) {
|
|
|
6445
6514
|
function resolveRunScript3(opts) {
|
|
6446
6515
|
if (opts.runScriptPath) return opts.runScriptPath;
|
|
6447
6516
|
const cwd = opts.cwd ?? process.cwd();
|
|
6448
|
-
return
|
|
6517
|
+
return path31.join(cwd, ".cleargate", "scripts", "run_script.sh");
|
|
6449
6518
|
}
|
|
6450
6519
|
function stateUpdateHandler(opts, cli) {
|
|
6451
6520
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -6502,21 +6571,21 @@ function stateValidateHandler(opts, cli) {
|
|
|
6502
6571
|
|
|
6503
6572
|
// src/commands/stamp-tokens.ts
|
|
6504
6573
|
init_cjs_shims();
|
|
6505
|
-
var
|
|
6506
|
-
var
|
|
6574
|
+
var fs31 = __toESM(require("fs"), 1);
|
|
6575
|
+
var path33 = __toESM(require("path"), 1);
|
|
6507
6576
|
|
|
6508
6577
|
// src/lib/ledger-reader.ts
|
|
6509
6578
|
init_cjs_shims();
|
|
6510
|
-
var
|
|
6511
|
-
var
|
|
6579
|
+
var fs30 = __toESM(require("fs"), 1);
|
|
6580
|
+
var path32 = __toESM(require("path"), 1);
|
|
6512
6581
|
function findSprintRunsRoot(startDir) {
|
|
6513
6582
|
let dir = startDir;
|
|
6514
6583
|
while (true) {
|
|
6515
|
-
const candidate =
|
|
6516
|
-
if (
|
|
6584
|
+
const candidate = path32.join(dir, ".cleargate", "sprint-runs");
|
|
6585
|
+
if (fs30.existsSync(candidate)) {
|
|
6517
6586
|
return candidate;
|
|
6518
6587
|
}
|
|
6519
|
-
const parent =
|
|
6588
|
+
const parent = path32.dirname(dir);
|
|
6520
6589
|
if (parent === dir) {
|
|
6521
6590
|
return null;
|
|
6522
6591
|
}
|
|
@@ -6569,13 +6638,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
6569
6638
|
}
|
|
6570
6639
|
sprintRunsRoot = found;
|
|
6571
6640
|
}
|
|
6572
|
-
if (!
|
|
6641
|
+
if (!fs30.existsSync(sprintRunsRoot)) {
|
|
6573
6642
|
return [];
|
|
6574
6643
|
}
|
|
6575
6644
|
let ledgerFiles;
|
|
6576
6645
|
try {
|
|
6577
|
-
const entries =
|
|
6578
|
-
ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
6646
|
+
const entries = fs30.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
6647
|
+
ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path32.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs30.existsSync(f));
|
|
6579
6648
|
} catch {
|
|
6580
6649
|
return [];
|
|
6581
6650
|
}
|
|
@@ -6583,7 +6652,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
|
|
|
6583
6652
|
for (const ledgerFile of ledgerFiles) {
|
|
6584
6653
|
let content;
|
|
6585
6654
|
try {
|
|
6586
|
-
content =
|
|
6655
|
+
content = fs30.readFileSync(ledgerFile, "utf-8");
|
|
6587
6656
|
} catch {
|
|
6588
6657
|
continue;
|
|
6589
6658
|
}
|
|
@@ -6634,7 +6703,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
6634
6703
|
});
|
|
6635
6704
|
const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
|
|
6636
6705
|
const cwd = cli?.cwd ?? process.cwd();
|
|
6637
|
-
const absPath =
|
|
6706
|
+
const absPath = path33.isAbsolute(file) ? file : path33.resolve(cwd, file);
|
|
6638
6707
|
if (/\/\.cleargate\/delivery\/archive\//.test(absPath)) {
|
|
6639
6708
|
stdoutFn(`[frozen] ${absPath}`);
|
|
6640
6709
|
exitFn(0);
|
|
@@ -6642,7 +6711,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
6642
6711
|
}
|
|
6643
6712
|
let rawContent;
|
|
6644
6713
|
try {
|
|
6645
|
-
rawContent =
|
|
6714
|
+
rawContent = fs31.readFileSync(absPath, "utf-8");
|
|
6646
6715
|
} catch {
|
|
6647
6716
|
stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
|
|
6648
6717
|
exitFn(1);
|
|
@@ -6714,7 +6783,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
6714
6783
|
return;
|
|
6715
6784
|
}
|
|
6716
6785
|
try {
|
|
6717
|
-
|
|
6786
|
+
fs31.writeFileSync(absPath, serialized, "utf-8");
|
|
6718
6787
|
} catch {
|
|
6719
6788
|
stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
|
|
6720
6789
|
exitFn(1);
|
|
@@ -6731,7 +6800,7 @@ function extractWorkItemId(fm, absPath) {
|
|
|
6731
6800
|
return val.trim();
|
|
6732
6801
|
}
|
|
6733
6802
|
}
|
|
6734
|
-
const basename12 =
|
|
6803
|
+
const basename12 = path33.basename(absPath);
|
|
6735
6804
|
const match = basename12.match(/^(STORY|EPIC|PROPOSAL|CR|BUG)-\d+(-\d+)?/i);
|
|
6736
6805
|
if (match) {
|
|
6737
6806
|
return match[0].toUpperCase();
|
|
@@ -6837,7 +6906,7 @@ ${body}`;
|
|
|
6837
6906
|
// src/commands/upgrade.ts
|
|
6838
6907
|
init_cjs_shims();
|
|
6839
6908
|
var fsp = __toESM(require("fs/promises"), 1);
|
|
6840
|
-
var
|
|
6909
|
+
var path34 = __toESM(require("path"), 1);
|
|
6841
6910
|
|
|
6842
6911
|
// src/lib/claude-md-surgery.ts
|
|
6843
6912
|
init_cjs_shims();
|
|
@@ -6987,7 +7056,7 @@ async function writeAtomic2(filePath, content) {
|
|
|
6987
7056
|
await fsp.rename(tmpPath, filePath);
|
|
6988
7057
|
}
|
|
6989
7058
|
async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
6990
|
-
const snapshotPath =
|
|
7059
|
+
const snapshotPath = path34.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
6991
7060
|
let snapshot;
|
|
6992
7061
|
try {
|
|
6993
7062
|
const raw = await fsp.readFile(snapshotPath, "utf-8");
|
|
@@ -7004,17 +7073,17 @@ async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
|
7004
7073
|
await writeAtomic2(snapshotPath, JSON.stringify(updated, null, 2) + "\n");
|
|
7005
7074
|
}
|
|
7006
7075
|
function isClaudeMd(filePath) {
|
|
7007
|
-
return
|
|
7076
|
+
return path34.basename(filePath) === "CLAUDE.md";
|
|
7008
7077
|
}
|
|
7009
7078
|
function isSettingsJson(filePath) {
|
|
7010
|
-
return
|
|
7079
|
+
return path34.basename(filePath) === "settings.json" && filePath.includes(".claude");
|
|
7011
7080
|
}
|
|
7012
7081
|
async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
7013
|
-
const targetPath =
|
|
7014
|
-
const sourcePath =
|
|
7082
|
+
const targetPath = path34.join(projectRoot, entry.path);
|
|
7083
|
+
const sourcePath = path34.join(packageRoot, entry.path);
|
|
7015
7084
|
try {
|
|
7016
7085
|
const pkgContent = await fsp.readFile(sourcePath, "utf-8");
|
|
7017
|
-
await fsp.mkdir(
|
|
7086
|
+
await fsp.mkdir(path34.dirname(targetPath), { recursive: true });
|
|
7018
7087
|
await writeAtomic2(targetPath, pkgContent);
|
|
7019
7088
|
await updateSnapshotEntry(projectRoot, entry.path, entry.sha256);
|
|
7020
7089
|
stdout(`[always] overwritten: ${entry.path}`);
|
|
@@ -7024,8 +7093,8 @@ async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
|
|
|
7024
7093
|
}
|
|
7025
7094
|
async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, currentSha, flags, opts) {
|
|
7026
7095
|
const { stdout, stderr, promptMergeChoiceFn, openInEditorFn, stdin } = opts;
|
|
7027
|
-
const targetPath =
|
|
7028
|
-
const sourcePath =
|
|
7096
|
+
const targetPath = path34.join(projectRoot, entry.path);
|
|
7097
|
+
const sourcePath = path34.join(packageRoot, entry.path);
|
|
7029
7098
|
let ours = "";
|
|
7030
7099
|
let theirs = "";
|
|
7031
7100
|
try {
|
|
@@ -7088,7 +7157,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
7088
7157
|
mergedContent = theirs;
|
|
7089
7158
|
}
|
|
7090
7159
|
}
|
|
7091
|
-
await fsp.mkdir(
|
|
7160
|
+
await fsp.mkdir(path34.dirname(targetPath), { recursive: true });
|
|
7092
7161
|
await writeAtomic2(targetPath, mergedContent);
|
|
7093
7162
|
const newSha2 = hashNormalized(mergedContent);
|
|
7094
7163
|
await updateSnapshotEntry(projectRoot, entry.path, newSha2);
|
|
@@ -7100,7 +7169,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
|
|
|
7100
7169
|
${ours}=======
|
|
7101
7170
|
${theirs}>>>>>>> theirs (upstream)
|
|
7102
7171
|
`;
|
|
7103
|
-
await fsp.mkdir(
|
|
7172
|
+
await fsp.mkdir(path34.dirname(mergeFilePath), { recursive: true });
|
|
7104
7173
|
await writeAtomic2(mergeFilePath, conflictContent);
|
|
7105
7174
|
try {
|
|
7106
7175
|
const result = await openInEditorFn(mergeFilePath);
|
|
@@ -7235,9 +7304,9 @@ async function upgradeHandler(flags, cli) {
|
|
|
7235
7304
|
|
|
7236
7305
|
// src/commands/uninstall.ts
|
|
7237
7306
|
init_cjs_shims();
|
|
7238
|
-
var
|
|
7307
|
+
var fs32 = __toESM(require("fs"), 1);
|
|
7239
7308
|
var fsp2 = __toESM(require("fs/promises"), 1);
|
|
7240
|
-
var
|
|
7309
|
+
var path35 = __toESM(require("path"), 1);
|
|
7241
7310
|
var import_node_child_process13 = require("child_process");
|
|
7242
7311
|
var USER_ARTIFACT_TIERS = ["user-artifact"];
|
|
7243
7312
|
var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
|
|
@@ -7263,10 +7332,10 @@ function shouldPreserve(entry, preserveSet, removeSet) {
|
|
|
7263
7332
|
return false;
|
|
7264
7333
|
}
|
|
7265
7334
|
function resolveProjectName(target) {
|
|
7266
|
-
const pkgPath =
|
|
7267
|
-
if (
|
|
7335
|
+
const pkgPath = path35.join(target, "package.json");
|
|
7336
|
+
if (fs32.existsSync(pkgPath)) {
|
|
7268
7337
|
try {
|
|
7269
|
-
const raw =
|
|
7338
|
+
const raw = fs32.readFileSync(pkgPath, "utf-8");
|
|
7270
7339
|
const parsed = JSON.parse(raw);
|
|
7271
7340
|
if (parsed.name && typeof parsed.name === "string") {
|
|
7272
7341
|
return parsed.name;
|
|
@@ -7274,7 +7343,7 @@ function resolveProjectName(target) {
|
|
|
7274
7343
|
} catch {
|
|
7275
7344
|
}
|
|
7276
7345
|
}
|
|
7277
|
-
return
|
|
7346
|
+
return path35.basename(target);
|
|
7278
7347
|
}
|
|
7279
7348
|
function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
7280
7349
|
const run = gitRunner ?? ((args) => {
|
|
@@ -7303,8 +7372,8 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
|
7303
7372
|
return changedFiles.filter((f) => manifestSet.has(f));
|
|
7304
7373
|
}
|
|
7305
7374
|
async function removeFromPackageJson(target, dryRun) {
|
|
7306
|
-
const pkgPath =
|
|
7307
|
-
if (!
|
|
7375
|
+
const pkgPath = path35.join(target, "package.json");
|
|
7376
|
+
if (!fs32.existsSync(pkgPath)) return false;
|
|
7308
7377
|
let raw;
|
|
7309
7378
|
try {
|
|
7310
7379
|
raw = await fsp2.readFile(pkgPath, "utf-8");
|
|
@@ -7345,7 +7414,7 @@ async function removeFile(filePath) {
|
|
|
7345
7414
|
}
|
|
7346
7415
|
async function removeDir(dirPath) {
|
|
7347
7416
|
try {
|
|
7348
|
-
|
|
7417
|
+
fs32.rmSync(dirPath, { recursive: true, force: true });
|
|
7349
7418
|
} catch {
|
|
7350
7419
|
}
|
|
7351
7420
|
}
|
|
@@ -7365,12 +7434,12 @@ async function uninstallHandler(opts) {
|
|
|
7365
7434
|
for (const t of FRAMEWORK_TIERS) removeSet.add(t);
|
|
7366
7435
|
for (const u of USER_ARTIFACT_TIERS) removeSet.add(u);
|
|
7367
7436
|
}
|
|
7368
|
-
const target = opts.path ?
|
|
7369
|
-
const cleargateDir =
|
|
7370
|
-
const manifestPath =
|
|
7371
|
-
const uninstalledPath =
|
|
7372
|
-
if (!
|
|
7373
|
-
if (
|
|
7437
|
+
const target = opts.path ? path35.resolve(opts.path) : cwd;
|
|
7438
|
+
const cleargateDir = path35.join(target, ".cleargate");
|
|
7439
|
+
const manifestPath = path35.join(cleargateDir, ".install-manifest.json");
|
|
7440
|
+
const uninstalledPath = path35.join(cleargateDir, ".uninstalled");
|
|
7441
|
+
if (!fs32.existsSync(manifestPath)) {
|
|
7442
|
+
if (fs32.existsSync(uninstalledPath)) {
|
|
7374
7443
|
stdout("already uninstalled");
|
|
7375
7444
|
exit(0);
|
|
7376
7445
|
return;
|
|
@@ -7379,7 +7448,7 @@ async function uninstallHandler(opts) {
|
|
|
7379
7448
|
exit(0);
|
|
7380
7449
|
return;
|
|
7381
7450
|
}
|
|
7382
|
-
if (
|
|
7451
|
+
if (fs32.existsSync(uninstalledPath) && !fs32.existsSync(manifestPath)) {
|
|
7383
7452
|
stdout("already uninstalled");
|
|
7384
7453
|
exit(0);
|
|
7385
7454
|
return;
|
|
@@ -7401,10 +7470,10 @@ async function uninstallHandler(opts) {
|
|
|
7401
7470
|
return;
|
|
7402
7471
|
}
|
|
7403
7472
|
}
|
|
7404
|
-
const claudeMdPath =
|
|
7473
|
+
const claudeMdPath = path35.join(target, "CLAUDE.md");
|
|
7405
7474
|
let claudeMdContent = null;
|
|
7406
|
-
if (
|
|
7407
|
-
claudeMdContent =
|
|
7475
|
+
if (fs32.existsSync(claudeMdPath)) {
|
|
7476
|
+
claudeMdContent = fs32.readFileSync(claudeMdPath, "utf-8");
|
|
7408
7477
|
if (!claudeMdContent.includes(CLEARGATE_START)) {
|
|
7409
7478
|
stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
|
|
7410
7479
|
exit(1);
|
|
@@ -7420,8 +7489,8 @@ async function uninstallHandler(opts) {
|
|
|
7420
7489
|
const toPreserve = [];
|
|
7421
7490
|
const toSkip = [];
|
|
7422
7491
|
for (const entry of snapshot.files) {
|
|
7423
|
-
const filePath =
|
|
7424
|
-
if (!
|
|
7492
|
+
const filePath = path35.join(target, entry.path);
|
|
7493
|
+
if (!fs32.existsSync(filePath)) {
|
|
7425
7494
|
toSkip.push(entry);
|
|
7426
7495
|
continue;
|
|
7427
7496
|
}
|
|
@@ -7478,7 +7547,7 @@ async function uninstallHandler(opts) {
|
|
|
7478
7547
|
const removedPaths = [];
|
|
7479
7548
|
const preservedPaths = [];
|
|
7480
7549
|
for (const entry of toRemove) {
|
|
7481
|
-
const filePath =
|
|
7550
|
+
const filePath = path35.join(target, entry.path);
|
|
7482
7551
|
await removeFile(filePath);
|
|
7483
7552
|
removedPaths.push(entry.path);
|
|
7484
7553
|
}
|
|
@@ -7494,10 +7563,10 @@ async function uninstallHandler(opts) {
|
|
|
7494
7563
|
stderr(`Warning: could not strip CLAUDE.md block: ${err.message}`);
|
|
7495
7564
|
}
|
|
7496
7565
|
}
|
|
7497
|
-
const settingsPath =
|
|
7498
|
-
if (
|
|
7566
|
+
const settingsPath = path35.join(target, ".claude", "settings.json");
|
|
7567
|
+
if (fs32.existsSync(settingsPath)) {
|
|
7499
7568
|
try {
|
|
7500
|
-
const raw =
|
|
7569
|
+
const raw = fs32.readFileSync(settingsPath, "utf-8");
|
|
7501
7570
|
const settings = JSON.parse(raw);
|
|
7502
7571
|
const cleaned = removeClearGateHooks(settings);
|
|
7503
7572
|
await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
|
|
@@ -7512,7 +7581,7 @@ async function uninstallHandler(opts) {
|
|
|
7512
7581
|
stdout("Removed @cleargate/cli from package.json. Run `npm install` to update package-lock.json.");
|
|
7513
7582
|
}
|
|
7514
7583
|
await removeFile(manifestPath);
|
|
7515
|
-
await removeFile(
|
|
7584
|
+
await removeFile(path35.join(cleargateDir, ".drift-state.json"));
|
|
7516
7585
|
const marker = {
|
|
7517
7586
|
uninstalled_at: now().toISOString(),
|
|
7518
7587
|
prior_version: snapshot.cleargate_version,
|
|
@@ -7537,30 +7606,30 @@ async function uninstallHandler(opts) {
|
|
|
7537
7606
|
// src/commands/sync.ts
|
|
7538
7607
|
init_cjs_shims();
|
|
7539
7608
|
var fsPromises8 = __toESM(require("fs/promises"), 1);
|
|
7540
|
-
var
|
|
7609
|
+
var path43 = __toESM(require("path"), 1);
|
|
7541
7610
|
|
|
7542
7611
|
// src/lib/sync-log.ts
|
|
7543
7612
|
init_cjs_shims();
|
|
7544
|
-
var
|
|
7613
|
+
var fs33 = __toESM(require("fs"), 1);
|
|
7545
7614
|
var fsPromises2 = __toESM(require("fs/promises"), 1);
|
|
7546
|
-
var
|
|
7615
|
+
var path36 = __toESM(require("path"), 1);
|
|
7547
7616
|
function resolveActiveSprintDir(projectRoot, _opts) {
|
|
7548
|
-
const sprintRunsRoot =
|
|
7549
|
-
const offSprint =
|
|
7550
|
-
if (!
|
|
7551
|
-
|
|
7552
|
-
|
|
7617
|
+
const sprintRunsRoot = path36.join(projectRoot, ".cleargate", "sprint-runs");
|
|
7618
|
+
const offSprint = path36.join(sprintRunsRoot, "_off-sprint");
|
|
7619
|
+
if (!fs33.existsSync(sprintRunsRoot)) {
|
|
7620
|
+
fs33.mkdirSync(sprintRunsRoot, { recursive: true });
|
|
7621
|
+
fs33.mkdirSync(offSprint, { recursive: true });
|
|
7553
7622
|
return offSprint;
|
|
7554
7623
|
}
|
|
7555
|
-
const entries =
|
|
7624
|
+
const entries = fs33.readdirSync(sprintRunsRoot, { withFileTypes: true });
|
|
7556
7625
|
const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
|
|
7557
|
-
const fullPath =
|
|
7558
|
-
const stat =
|
|
7626
|
+
const fullPath = path36.join(sprintRunsRoot, e.name);
|
|
7627
|
+
const stat = fs33.statSync(fullPath);
|
|
7559
7628
|
return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
|
|
7560
7629
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
7561
7630
|
if (sprintDirs.length === 0) {
|
|
7562
|
-
if (!
|
|
7563
|
-
|
|
7631
|
+
if (!fs33.existsSync(offSprint)) {
|
|
7632
|
+
fs33.mkdirSync(offSprint, { recursive: true });
|
|
7564
7633
|
}
|
|
7565
7634
|
return offSprint;
|
|
7566
7635
|
}
|
|
@@ -7571,7 +7640,7 @@ function redactDetail(detail) {
|
|
|
7571
7640
|
return detail.replace(/eyJ[A-Za-z0-9._-]+/g, "[REDACTED]");
|
|
7572
7641
|
}
|
|
7573
7642
|
async function appendSyncLog(sprintRoot, entry) {
|
|
7574
|
-
const logPath =
|
|
7643
|
+
const logPath = path36.join(sprintRoot, "sync-log.jsonl");
|
|
7575
7644
|
await fsPromises2.mkdir(sprintRoot, { recursive: true });
|
|
7576
7645
|
const safeEntry = {
|
|
7577
7646
|
...entry,
|
|
@@ -7581,7 +7650,7 @@ async function appendSyncLog(sprintRoot, entry) {
|
|
|
7581
7650
|
await fsPromises2.appendFile(logPath, line, { encoding: "utf8" });
|
|
7582
7651
|
}
|
|
7583
7652
|
async function readSyncLog(sprintRoot, filters) {
|
|
7584
|
-
const logPath =
|
|
7653
|
+
const logPath = path36.join(sprintRoot, "sync-log.jsonl");
|
|
7585
7654
|
let raw;
|
|
7586
7655
|
try {
|
|
7587
7656
|
raw = await fsPromises2.readFile(logPath, "utf8");
|
|
@@ -7687,7 +7756,7 @@ function classify2(local, remote, since) {
|
|
|
7687
7756
|
init_cjs_shims();
|
|
7688
7757
|
var import_node_fs2 = require("fs");
|
|
7689
7758
|
var os6 = __toESM(require("os"), 1);
|
|
7690
|
-
var
|
|
7759
|
+
var path37 = __toESM(require("path"), 1);
|
|
7691
7760
|
function promptFourChoice(opts) {
|
|
7692
7761
|
const { stdin, stdout } = opts;
|
|
7693
7762
|
stdout("[k]eep mine / [t]ake theirs / [e]dit in $EDITOR / [a]bort: ");
|
|
@@ -7757,7 +7826,7 @@ async function promptThreeWayMerge(opts) {
|
|
|
7757
7826
|
case "a":
|
|
7758
7827
|
return { resolution: "aborted", body: local };
|
|
7759
7828
|
case "e": {
|
|
7760
|
-
const tmpFile =
|
|
7829
|
+
const tmpFile = path37.join(os6.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
|
|
7761
7830
|
const markerContent = `<<<<<<< local
|
|
7762
7831
|
${local}
|
|
7763
7832
|
=======
|
|
@@ -7857,12 +7926,12 @@ init_config();
|
|
|
7857
7926
|
// src/lib/intake.ts
|
|
7858
7927
|
init_cjs_shims();
|
|
7859
7928
|
var fsPromises4 = __toESM(require("fs/promises"), 1);
|
|
7860
|
-
var
|
|
7929
|
+
var path39 = __toESM(require("path"), 1);
|
|
7861
7930
|
|
|
7862
7931
|
// src/lib/slug.ts
|
|
7863
7932
|
init_cjs_shims();
|
|
7864
7933
|
var fsPromises3 = __toESM(require("fs/promises"), 1);
|
|
7865
|
-
var
|
|
7934
|
+
var path38 = __toESM(require("path"), 1);
|
|
7866
7935
|
function slugify(title, max = 40) {
|
|
7867
7936
|
const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
7868
7937
|
const lowered = normalized.toLowerCase();
|
|
@@ -7877,8 +7946,8 @@ function slugify(title, max = 40) {
|
|
|
7877
7946
|
var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
|
|
7878
7947
|
async function nextProposalId(projectRoot) {
|
|
7879
7948
|
const dirs = [
|
|
7880
|
-
|
|
7881
|
-
|
|
7949
|
+
path38.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
7950
|
+
path38.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
7882
7951
|
];
|
|
7883
7952
|
let maxN = 0;
|
|
7884
7953
|
for (const dir of dirs) {
|
|
@@ -7890,7 +7959,7 @@ async function nextProposalId(projectRoot) {
|
|
|
7890
7959
|
}
|
|
7891
7960
|
for (const entry of entries) {
|
|
7892
7961
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7893
|
-
const fullPath =
|
|
7962
|
+
const fullPath = path38.join(dir, entry.name);
|
|
7894
7963
|
try {
|
|
7895
7964
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
7896
7965
|
const fmEnd = extractFrontmatterBlock(raw);
|
|
@@ -7908,8 +7977,8 @@ async function nextProposalId(projectRoot) {
|
|
|
7908
7977
|
}
|
|
7909
7978
|
async function findByRemoteId(projectRoot, remoteId) {
|
|
7910
7979
|
const dirs = [
|
|
7911
|
-
|
|
7912
|
-
|
|
7980
|
+
path38.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
7981
|
+
path38.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
7913
7982
|
];
|
|
7914
7983
|
const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7915
7984
|
const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
|
|
@@ -7922,7 +7991,7 @@ async function findByRemoteId(projectRoot, remoteId) {
|
|
|
7922
7991
|
}
|
|
7923
7992
|
for (const entry of entries) {
|
|
7924
7993
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7925
|
-
const fullPath =
|
|
7994
|
+
const fullPath = path38.join(dir, entry.name);
|
|
7926
7995
|
try {
|
|
7927
7996
|
const raw = await fsPromises3.readFile(fullPath, "utf8");
|
|
7928
7997
|
const fm = extractFrontmatterBlock(raw);
|
|
@@ -7951,7 +8020,7 @@ function extractFrontmatterBlock(raw) {
|
|
|
7951
8020
|
// src/lib/intake.ts
|
|
7952
8021
|
async function runIntakeBranch(opts) {
|
|
7953
8022
|
const {
|
|
7954
|
-
mcp,
|
|
8023
|
+
mcp: mcp2,
|
|
7955
8024
|
identity,
|
|
7956
8025
|
sprintRoot,
|
|
7957
8026
|
projectRoot,
|
|
@@ -7959,10 +8028,10 @@ async function runIntakeBranch(opts) {
|
|
|
7959
8028
|
labelFilter = "cleargate:proposal",
|
|
7960
8029
|
now = () => (/* @__PURE__ */ new Date()).toISOString()
|
|
7961
8030
|
} = opts;
|
|
7962
|
-
const pendingSyncDir =
|
|
8031
|
+
const pendingSyncDir = path39.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
7963
8032
|
let remoteItems = [];
|
|
7964
8033
|
try {
|
|
7965
|
-
remoteItems = await
|
|
8034
|
+
remoteItems = await mcp2.call(
|
|
7966
8035
|
"cleargate_detect_new_items",
|
|
7967
8036
|
{ label: labelFilter }
|
|
7968
8037
|
);
|
|
@@ -7990,7 +8059,7 @@ async function runIntakeBranch(opts) {
|
|
|
7990
8059
|
const slug2 = slugify(item.title ?? "untitled");
|
|
7991
8060
|
const num2 = proposalId2.replace("PROP-", "");
|
|
7992
8061
|
const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
|
|
7993
|
-
const targetPath2 =
|
|
8062
|
+
const targetPath2 = path39.join(pendingSyncDir, filename2);
|
|
7994
8063
|
createdItems.push({
|
|
7995
8064
|
proposalId: proposalId2,
|
|
7996
8065
|
remoteId: item.remote_id,
|
|
@@ -8003,7 +8072,7 @@ async function runIntakeBranch(opts) {
|
|
|
8003
8072
|
const num = proposalId.replace("PROP-", "");
|
|
8004
8073
|
const slug = slugify(item.title ?? "untitled");
|
|
8005
8074
|
const filename = `PROPOSAL-${num}-remote-${slug}.md`;
|
|
8006
|
-
const targetPath =
|
|
8075
|
+
const targetPath = path39.join(pendingSyncDir, filename);
|
|
8007
8076
|
const nowTs = now();
|
|
8008
8077
|
const fm = {
|
|
8009
8078
|
proposal_id: proposalId,
|
|
@@ -8095,8 +8164,8 @@ path/to/new/file.ext - {Explanation of purpose}
|
|
|
8095
8164
|
}
|
|
8096
8165
|
async function hasAnyRemoteAuthored(projectRoot) {
|
|
8097
8166
|
const dirs = [
|
|
8098
|
-
|
|
8099
|
-
|
|
8167
|
+
path39.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
|
|
8168
|
+
path39.join(projectRoot, ".cleargate", "delivery", "archive")
|
|
8100
8169
|
];
|
|
8101
8170
|
for (const dir of dirs) {
|
|
8102
8171
|
let entries;
|
|
@@ -8107,7 +8176,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
8107
8176
|
}
|
|
8108
8177
|
for (const entry of entries) {
|
|
8109
8178
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8110
|
-
const fullPath =
|
|
8179
|
+
const fullPath = path39.join(dir, entry.name);
|
|
8111
8180
|
try {
|
|
8112
8181
|
const raw = await fsPromises4.readFile(fullPath, "utf8");
|
|
8113
8182
|
const fmEnd = raw.indexOf("\n---", 4);
|
|
@@ -8125,9 +8194,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
|
|
|
8125
8194
|
|
|
8126
8195
|
// src/lib/active-criteria.ts
|
|
8127
8196
|
init_cjs_shims();
|
|
8128
|
-
var
|
|
8197
|
+
var fs35 = __toESM(require("fs"), 1);
|
|
8129
8198
|
var fsPromises5 = __toESM(require("fs/promises"), 1);
|
|
8130
|
-
var
|
|
8199
|
+
var path40 = __toESM(require("path"), 1);
|
|
8131
8200
|
async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
8132
8201
|
const active = /* @__PURE__ */ new Set();
|
|
8133
8202
|
const now = Date.parse(nowFn());
|
|
@@ -8152,7 +8221,7 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
8152
8221
|
const ids = /* @__PURE__ */ new Set();
|
|
8153
8222
|
try {
|
|
8154
8223
|
const sprintDir = resolveActiveSprintDir(projectRoot);
|
|
8155
|
-
const sprintId =
|
|
8224
|
+
const sprintId = path40.basename(sprintDir);
|
|
8156
8225
|
if (sprintId === "_off-sprint") return ids;
|
|
8157
8226
|
const sprintFile = await findSprintFile(projectRoot, sprintId);
|
|
8158
8227
|
if (!sprintFile) return ids;
|
|
@@ -8167,14 +8236,14 @@ async function resolveInSprintIds(projectRoot) {
|
|
|
8167
8236
|
return ids;
|
|
8168
8237
|
}
|
|
8169
8238
|
async function findSprintFile(projectRoot, sprintId) {
|
|
8170
|
-
const pendingSync =
|
|
8171
|
-
const archive =
|
|
8239
|
+
const pendingSync = path40.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8240
|
+
const archive = path40.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
8172
8241
|
for (const dir of [pendingSync, archive]) {
|
|
8173
8242
|
try {
|
|
8174
|
-
const entries =
|
|
8243
|
+
const entries = fs35.readdirSync(dir, { withFileTypes: true });
|
|
8175
8244
|
for (const entry of entries) {
|
|
8176
8245
|
if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
|
|
8177
|
-
return
|
|
8246
|
+
return path40.join(dir, entry.name);
|
|
8178
8247
|
}
|
|
8179
8248
|
}
|
|
8180
8249
|
} catch {
|
|
@@ -8186,12 +8255,12 @@ async function findSprintFile(projectRoot, sprintId) {
|
|
|
8186
8255
|
// src/lib/comments-cache.ts
|
|
8187
8256
|
init_cjs_shims();
|
|
8188
8257
|
var fsPromises6 = __toESM(require("fs/promises"), 1);
|
|
8189
|
-
var
|
|
8258
|
+
var path41 = __toESM(require("path"), 1);
|
|
8190
8259
|
function cacheDir(projectRoot) {
|
|
8191
|
-
return
|
|
8260
|
+
return path41.join(projectRoot, ".cleargate", ".comments-cache");
|
|
8192
8261
|
}
|
|
8193
8262
|
function cachePath(projectRoot, remoteId) {
|
|
8194
|
-
return
|
|
8263
|
+
return path41.join(cacheDir(projectRoot), `${remoteId}.json`);
|
|
8195
8264
|
}
|
|
8196
8265
|
async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
8197
8266
|
const dir = cacheDir(projectRoot);
|
|
@@ -8206,7 +8275,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
|
|
|
8206
8275
|
// src/lib/wiki-comments-render.ts
|
|
8207
8276
|
init_cjs_shims();
|
|
8208
8277
|
var fsPromises7 = __toESM(require("fs/promises"), 1);
|
|
8209
|
-
var
|
|
8278
|
+
var path42 = __toESM(require("path"), 1);
|
|
8210
8279
|
var START = "<!-- cleargate:comments:start -->";
|
|
8211
8280
|
var END = "<!-- cleargate:comments:end -->";
|
|
8212
8281
|
function resolveBucket(fm) {
|
|
@@ -8251,7 +8320,7 @@ async function renderCommentsSection(opts) {
|
|
|
8251
8320
|
const bucket = resolveBucket(localItem.fm);
|
|
8252
8321
|
const primaryId = getPrimaryId(localItem.fm);
|
|
8253
8322
|
if (!bucket || !primaryId) return;
|
|
8254
|
-
const wikiPath =
|
|
8323
|
+
const wikiPath = path42.join(
|
|
8255
8324
|
projectRoot,
|
|
8256
8325
|
".cleargate",
|
|
8257
8326
|
"wiki",
|
|
@@ -8287,7 +8356,7 @@ async function renderCommentsSection(opts) {
|
|
|
8287
8356
|
await writeAtomic4(wikiPath, updated);
|
|
8288
8357
|
}
|
|
8289
8358
|
async function writeAtomic4(filePath, content) {
|
|
8290
|
-
await fsPromises7.mkdir(
|
|
8359
|
+
await fsPromises7.mkdir(path42.dirname(filePath), { recursive: true });
|
|
8291
8360
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
8292
8361
|
await fsPromises7.writeFile(tmpPath, content, "utf8");
|
|
8293
8362
|
await fsPromises7.rename(tmpPath, filePath);
|
|
@@ -8299,11 +8368,11 @@ async function syncCheckHandler(opts = {}) {
|
|
|
8299
8368
|
const env = opts.env ?? process.env;
|
|
8300
8369
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
8301
8370
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8302
|
-
const markerPath =
|
|
8371
|
+
const markerPath = path43.join(projectRoot, ".cleargate", ".sync-marker.json");
|
|
8303
8372
|
const updateMarker = async (nowIso2) => {
|
|
8304
8373
|
try {
|
|
8305
8374
|
const content = JSON.stringify({ last_check: nowIso2 });
|
|
8306
|
-
await fsPromises8.mkdir(
|
|
8375
|
+
await fsPromises8.mkdir(path43.dirname(markerPath), { recursive: true });
|
|
8307
8376
|
const tmpPath = `${markerPath}.tmp.${Date.now()}`;
|
|
8308
8377
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
8309
8378
|
await fsPromises8.rename(tmpPath, markerPath);
|
|
@@ -8324,9 +8393,9 @@ async function syncCheckHandler(opts = {}) {
|
|
|
8324
8393
|
}
|
|
8325
8394
|
} catch {
|
|
8326
8395
|
}
|
|
8327
|
-
let
|
|
8396
|
+
let mcp2;
|
|
8328
8397
|
if (opts.mcp) {
|
|
8329
|
-
|
|
8398
|
+
mcp2 = opts.mcp;
|
|
8330
8399
|
} else {
|
|
8331
8400
|
let baseUrl = env["CLEARGATE_MCP_URL"];
|
|
8332
8401
|
if (!baseUrl || !baseUrl.trim()) {
|
|
@@ -8355,10 +8424,10 @@ async function syncCheckHandler(opts = {}) {
|
|
|
8355
8424
|
await emitError("adapter-not-configured", nowIso);
|
|
8356
8425
|
return;
|
|
8357
8426
|
}
|
|
8358
|
-
|
|
8427
|
+
mcp2 = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
|
|
8359
8428
|
}
|
|
8360
8429
|
try {
|
|
8361
|
-
const adapterInfo = await
|
|
8430
|
+
const adapterInfo = await mcp2.adapterInfo();
|
|
8362
8431
|
if (!adapterInfo.configured || adapterInfo.name === "no-adapter-configured") {
|
|
8363
8432
|
await emitError("adapter-not-configured", nowIso);
|
|
8364
8433
|
return;
|
|
@@ -8367,7 +8436,7 @@ async function syncCheckHandler(opts = {}) {
|
|
|
8367
8436
|
}
|
|
8368
8437
|
let refs;
|
|
8369
8438
|
try {
|
|
8370
|
-
refs = await
|
|
8439
|
+
refs = await mcp2.call("cleargate_list_remote_updates", { since });
|
|
8371
8440
|
} catch (err) {
|
|
8372
8441
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8373
8442
|
await emitError(msg, nowIso);
|
|
@@ -8386,10 +8455,10 @@ async function syncHandler(opts = {}) {
|
|
|
8386
8455
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8387
8456
|
const identity = resolveIdentity(projectRoot);
|
|
8388
8457
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
8389
|
-
const sprintId =
|
|
8390
|
-
let
|
|
8458
|
+
const sprintId = path43.basename(sprintRoot);
|
|
8459
|
+
let mcp2;
|
|
8391
8460
|
if (opts.mcp) {
|
|
8392
|
-
|
|
8461
|
+
mcp2 = opts.mcp;
|
|
8393
8462
|
} else {
|
|
8394
8463
|
let baseUrl = env["CLEARGATE_MCP_URL"];
|
|
8395
8464
|
if (!baseUrl || !baseUrl.trim()) {
|
|
@@ -8432,11 +8501,11 @@ async function syncHandler(opts = {}) {
|
|
|
8432
8501
|
exit(2);
|
|
8433
8502
|
return;
|
|
8434
8503
|
}
|
|
8435
|
-
|
|
8504
|
+
mcp2 = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
|
|
8436
8505
|
}
|
|
8437
8506
|
let adapterInfo;
|
|
8438
8507
|
try {
|
|
8439
|
-
adapterInfo = await
|
|
8508
|
+
adapterInfo = await mcp2.adapterInfo();
|
|
8440
8509
|
} catch {
|
|
8441
8510
|
adapterInfo = { configured: true, name: "unknown" };
|
|
8442
8511
|
}
|
|
@@ -8447,7 +8516,7 @@ async function syncHandler(opts = {}) {
|
|
|
8447
8516
|
exit(2);
|
|
8448
8517
|
return;
|
|
8449
8518
|
}
|
|
8450
|
-
const wikiMetaPath =
|
|
8519
|
+
const wikiMetaPath = path43.join(projectRoot, ".cleargate", "wiki", "meta.json");
|
|
8451
8520
|
let lastRemoteSync = "1970-01-01T00:00:00.000Z";
|
|
8452
8521
|
try {
|
|
8453
8522
|
const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -8457,13 +8526,13 @@ async function syncHandler(opts = {}) {
|
|
|
8457
8526
|
}
|
|
8458
8527
|
} catch {
|
|
8459
8528
|
}
|
|
8460
|
-
const remoteRefs = await
|
|
8529
|
+
const remoteRefs = await mcp2.call(
|
|
8461
8530
|
"cleargate_list_remote_updates",
|
|
8462
8531
|
{ since: lastRemoteSync }
|
|
8463
8532
|
);
|
|
8464
8533
|
const pulled = [];
|
|
8465
8534
|
for (const ref of remoteRefs) {
|
|
8466
|
-
const item = await
|
|
8535
|
+
const item = await mcp2.call(
|
|
8467
8536
|
"cleargate_pull_item",
|
|
8468
8537
|
{ remote_id: ref.remote_id }
|
|
8469
8538
|
);
|
|
@@ -8475,7 +8544,7 @@ async function syncHandler(opts = {}) {
|
|
|
8475
8544
|
let intakeResult = { created: 0, items: [] };
|
|
8476
8545
|
try {
|
|
8477
8546
|
intakeResult = await runIntakeBranch({
|
|
8478
|
-
mcp,
|
|
8547
|
+
mcp: mcp2,
|
|
8479
8548
|
identity,
|
|
8480
8549
|
sprintRoot,
|
|
8481
8550
|
projectRoot,
|
|
@@ -8501,7 +8570,7 @@ async function syncHandler(opts = {}) {
|
|
|
8501
8570
|
const activeSet = await resolveActiveItems(projectRoot, localRefs, nowFn);
|
|
8502
8571
|
for (const remoteId of activeSet) {
|
|
8503
8572
|
try {
|
|
8504
|
-
const comments = await
|
|
8573
|
+
const comments = await mcp2.call(
|
|
8505
8574
|
"cleargate_pull_comments",
|
|
8506
8575
|
{ remote_id: remoteId }
|
|
8507
8576
|
);
|
|
@@ -8655,7 +8724,7 @@ async function syncHandler(opts = {}) {
|
|
|
8655
8724
|
await appendSyncLog(sprintRoot, entry);
|
|
8656
8725
|
}
|
|
8657
8726
|
for (const { localPath, fm, body, itemId } of pushQueue) {
|
|
8658
|
-
await
|
|
8727
|
+
await mcp2.call("push_item", {
|
|
8659
8728
|
cleargate_id: itemId,
|
|
8660
8729
|
type: typeof fm["story_id"] === "string" ? "story" : typeof fm["epic_id"] === "string" ? "epic" : typeof fm["proposal_id"] === "string" ? "proposal" : "story",
|
|
8661
8730
|
payload: fm
|
|
@@ -8688,7 +8757,7 @@ async function syncHandler(opts = {}) {
|
|
|
8688
8757
|
};
|
|
8689
8758
|
await appendSyncLog(sprintRoot, entry);
|
|
8690
8759
|
}
|
|
8691
|
-
const conflictsFile =
|
|
8760
|
+
const conflictsFile = path43.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
8692
8761
|
const conflictsContent = {
|
|
8693
8762
|
generated_at: nowFn(),
|
|
8694
8763
|
sprint_id: sprintId,
|
|
@@ -8696,7 +8765,7 @@ async function syncHandler(opts = {}) {
|
|
|
8696
8765
|
};
|
|
8697
8766
|
await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
|
|
8698
8767
|
try {
|
|
8699
|
-
await fsPromises8.mkdir(
|
|
8768
|
+
await fsPromises8.mkdir(path43.dirname(wikiMetaPath), { recursive: true });
|
|
8700
8769
|
let meta = {};
|
|
8701
8770
|
try {
|
|
8702
8771
|
const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
|
|
@@ -8737,13 +8806,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
|
|
|
8737
8806
|
await writeAtomic5(localPath, newContent);
|
|
8738
8807
|
}
|
|
8739
8808
|
async function writeAtomic5(filePath, content) {
|
|
8740
|
-
await fsPromises8.mkdir(
|
|
8809
|
+
await fsPromises8.mkdir(path43.dirname(filePath), { recursive: true });
|
|
8741
8810
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
8742
8811
|
await fsPromises8.writeFile(tmpPath, content, "utf8");
|
|
8743
8812
|
await fsPromises8.rename(tmpPath, filePath);
|
|
8744
8813
|
}
|
|
8745
8814
|
async function scanLocalItems(projectRoot) {
|
|
8746
|
-
const pendingSync =
|
|
8815
|
+
const pendingSync = path43.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8747
8816
|
const results = [];
|
|
8748
8817
|
let entries;
|
|
8749
8818
|
try {
|
|
@@ -8753,7 +8822,7 @@ async function scanLocalItems(projectRoot) {
|
|
|
8753
8822
|
}
|
|
8754
8823
|
for (const entry of entries) {
|
|
8755
8824
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8756
|
-
const fullPath =
|
|
8825
|
+
const fullPath = path43.join(pendingSync, entry.name);
|
|
8757
8826
|
try {
|
|
8758
8827
|
const raw = await fsPromises8.readFile(fullPath, "utf8");
|
|
8759
8828
|
const { fm, body } = parseFrontmatter(raw);
|
|
@@ -8776,7 +8845,7 @@ function getItemId(fm) {
|
|
|
8776
8845
|
// src/commands/pull.ts
|
|
8777
8846
|
init_cjs_shims();
|
|
8778
8847
|
var fsPromises9 = __toESM(require("fs/promises"), 1);
|
|
8779
|
-
var
|
|
8848
|
+
var path44 = __toESM(require("path"), 1);
|
|
8780
8849
|
init_acquire();
|
|
8781
8850
|
init_config();
|
|
8782
8851
|
async function pullHandler(idOrRemoteId, opts = {}) {
|
|
@@ -8788,9 +8857,9 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
8788
8857
|
const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
8789
8858
|
const identity = resolveIdentity(projectRoot);
|
|
8790
8859
|
const sprintRoot = resolveActiveSprintDir(projectRoot);
|
|
8791
|
-
let
|
|
8860
|
+
let mcp2;
|
|
8792
8861
|
if (opts.mcp) {
|
|
8793
|
-
|
|
8862
|
+
mcp2 = opts.mcp;
|
|
8794
8863
|
} else {
|
|
8795
8864
|
let baseUrl = env["CLEARGATE_MCP_URL"];
|
|
8796
8865
|
if (!baseUrl || !baseUrl.trim()) {
|
|
@@ -8825,7 +8894,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
8825
8894
|
exit(2);
|
|
8826
8895
|
return;
|
|
8827
8896
|
}
|
|
8828
|
-
|
|
8897
|
+
mcp2 = createMcpClient({ baseUrl: baseUrl.trim(), token: accessToken });
|
|
8829
8898
|
}
|
|
8830
8899
|
const remoteId = await resolveRemoteId(idOrRemoteId, projectRoot);
|
|
8831
8900
|
if (!remoteId) {
|
|
@@ -8834,7 +8903,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
8834
8903
|
exit(1);
|
|
8835
8904
|
return;
|
|
8836
8905
|
}
|
|
8837
|
-
const remoteItem = await
|
|
8906
|
+
const remoteItem = await mcp2.call("cleargate_pull_item", { remote_id: remoteId });
|
|
8838
8907
|
if (!remoteItem) {
|
|
8839
8908
|
stderr(`Error: item ${remoteId} not found on MCP server.
|
|
8840
8909
|
`);
|
|
@@ -8891,10 +8960,10 @@ async function pullHandler(idOrRemoteId, opts = {}) {
|
|
|
8891
8960
|
result: "ok"
|
|
8892
8961
|
};
|
|
8893
8962
|
await appendSyncLog(sprintRoot, entry);
|
|
8894
|
-
stdout(`pull: ${remoteId} applied to ${
|
|
8963
|
+
stdout(`pull: ${remoteId} applied to ${path44.relative(projectRoot, localPath)}
|
|
8895
8964
|
`);
|
|
8896
8965
|
if (opts.comments) {
|
|
8897
|
-
const comments = await
|
|
8966
|
+
const comments = await mcp2.call(
|
|
8898
8967
|
"cleargate_pull_comments",
|
|
8899
8968
|
{ remote_id: remoteId }
|
|
8900
8969
|
);
|
|
@@ -8914,7 +8983,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
8914
8983
|
if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
|
|
8915
8984
|
return idOrRemoteId;
|
|
8916
8985
|
}
|
|
8917
|
-
const pendingSync =
|
|
8986
|
+
const pendingSync = path44.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8918
8987
|
let entries;
|
|
8919
8988
|
try {
|
|
8920
8989
|
entries = await fsPromises9.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -8924,7 +8993,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
8924
8993
|
for (const entry of entries) {
|
|
8925
8994
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8926
8995
|
try {
|
|
8927
|
-
const raw = await fsPromises9.readFile(
|
|
8996
|
+
const raw = await fsPromises9.readFile(path44.join(pendingSync, entry.name), "utf8");
|
|
8928
8997
|
const { fm } = parseFrontmatter(raw);
|
|
8929
8998
|
for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
|
|
8930
8999
|
if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
|
|
@@ -8937,7 +9006,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
|
|
|
8937
9006
|
return null;
|
|
8938
9007
|
}
|
|
8939
9008
|
async function findLocalFile(remoteId, projectRoot) {
|
|
8940
|
-
const pendingSync =
|
|
9009
|
+
const pendingSync = path44.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
8941
9010
|
let entries;
|
|
8942
9011
|
try {
|
|
8943
9012
|
entries = await fsPromises9.readdir(pendingSync, { withFileTypes: true });
|
|
@@ -8946,7 +9015,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
8946
9015
|
}
|
|
8947
9016
|
for (const entry of entries) {
|
|
8948
9017
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8949
|
-
const fullPath =
|
|
9018
|
+
const fullPath = path44.join(pendingSync, entry.name);
|
|
8950
9019
|
try {
|
|
8951
9020
|
const raw = await fsPromises9.readFile(fullPath, "utf8");
|
|
8952
9021
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -8957,7 +9026,7 @@ async function findLocalFile(remoteId, projectRoot) {
|
|
|
8957
9026
|
return null;
|
|
8958
9027
|
}
|
|
8959
9028
|
async function writeAtomic6(filePath, content) {
|
|
8960
|
-
await fsPromises9.mkdir(
|
|
9029
|
+
await fsPromises9.mkdir(path44.dirname(filePath), { recursive: true });
|
|
8961
9030
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
8962
9031
|
await fsPromises9.writeFile(tmpPath, content, "utf8");
|
|
8963
9032
|
await fsPromises9.rename(tmpPath, filePath);
|
|
@@ -8973,7 +9042,7 @@ function getItemId2(fm) {
|
|
|
8973
9042
|
// src/commands/push.ts
|
|
8974
9043
|
init_cjs_shims();
|
|
8975
9044
|
var fsPromises10 = __toESM(require("fs/promises"), 1);
|
|
8976
|
-
var
|
|
9045
|
+
var path45 = __toESM(require("path"), 1);
|
|
8977
9046
|
init_acquire();
|
|
8978
9047
|
init_config();
|
|
8979
9048
|
async function pushHandler(fileOrId, opts = {}) {
|
|
@@ -9049,7 +9118,7 @@ async function pushHandler(fileOrId, opts = {}) {
|
|
|
9049
9118
|
}
|
|
9050
9119
|
async function handlePush(filePath, ctx) {
|
|
9051
9120
|
const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
|
|
9052
|
-
const resolvedPath =
|
|
9121
|
+
const resolvedPath = path45.isAbsolute(filePath) ? filePath : path45.resolve(projectRoot, filePath);
|
|
9053
9122
|
let rawContent;
|
|
9054
9123
|
try {
|
|
9055
9124
|
rawContent = await fsPromises10.readFile(resolvedPath, "utf8");
|
|
@@ -9092,10 +9161,10 @@ async function handlePush(filePath, ctx) {
|
|
|
9092
9161
|
if (h1) payloadForPush["title"] = h1;
|
|
9093
9162
|
}
|
|
9094
9163
|
payloadForPush["body"] = body;
|
|
9095
|
-
const
|
|
9164
|
+
const mcp2 = await resolveMcp();
|
|
9096
9165
|
let result;
|
|
9097
9166
|
try {
|
|
9098
|
-
result = await
|
|
9167
|
+
result = await mcp2.call("push_item", {
|
|
9099
9168
|
cleargate_id: itemId,
|
|
9100
9169
|
type,
|
|
9101
9170
|
payload: payloadForPush,
|
|
@@ -9147,9 +9216,9 @@ async function handleRevert(idOrRemoteId, ctx) {
|
|
|
9147
9216
|
exit(1);
|
|
9148
9217
|
return;
|
|
9149
9218
|
}
|
|
9150
|
-
const
|
|
9219
|
+
const mcp2 = await resolveMcp();
|
|
9151
9220
|
try {
|
|
9152
|
-
await
|
|
9221
|
+
await mcp2.call("sync_status", {
|
|
9153
9222
|
cleargate_id: itemId,
|
|
9154
9223
|
new_status: "archived-without-shipping"
|
|
9155
9224
|
});
|
|
@@ -9175,8 +9244,8 @@ async function handleRevert(idOrRemoteId, ctx) {
|
|
|
9175
9244
|
void localPath;
|
|
9176
9245
|
}
|
|
9177
9246
|
async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
9178
|
-
const pendingSync =
|
|
9179
|
-
const archive =
|
|
9247
|
+
const pendingSync = path45.join(projectRoot, ".cleargate", "delivery", "pending-sync");
|
|
9248
|
+
const archive = path45.join(projectRoot, ".cleargate", "delivery", "archive");
|
|
9180
9249
|
for (const dir of [pendingSync, archive]) {
|
|
9181
9250
|
let entries;
|
|
9182
9251
|
try {
|
|
@@ -9186,7 +9255,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
9186
9255
|
}
|
|
9187
9256
|
for (const entry of entries) {
|
|
9188
9257
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
9189
|
-
const fullPath =
|
|
9258
|
+
const fullPath = path45.join(dir, entry.name);
|
|
9190
9259
|
try {
|
|
9191
9260
|
const raw = await fsPromises10.readFile(fullPath, "utf8");
|
|
9192
9261
|
const { fm } = parseFrontmatter(raw);
|
|
@@ -9205,7 +9274,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
|
|
|
9205
9274
|
return null;
|
|
9206
9275
|
}
|
|
9207
9276
|
async function writeAtomic7(filePath, content) {
|
|
9208
|
-
await fsPromises10.mkdir(
|
|
9277
|
+
await fsPromises10.mkdir(path45.dirname(filePath), { recursive: true });
|
|
9209
9278
|
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
9210
9279
|
await fsPromises10.writeFile(tmpPath, content, "utf8");
|
|
9211
9280
|
await fsPromises10.rename(tmpPath, filePath);
|
|
@@ -9234,7 +9303,7 @@ function getItemType(fm) {
|
|
|
9234
9303
|
// src/commands/conflicts.ts
|
|
9235
9304
|
init_cjs_shims();
|
|
9236
9305
|
var fsPromises11 = __toESM(require("fs/promises"), 1);
|
|
9237
|
-
var
|
|
9306
|
+
var path46 = __toESM(require("path"), 1);
|
|
9238
9307
|
init_acquire();
|
|
9239
9308
|
init_config();
|
|
9240
9309
|
var RESOLUTION_HINTS = {
|
|
@@ -9272,7 +9341,7 @@ async function conflictsHandler(opts = {}) {
|
|
|
9272
9341
|
}
|
|
9273
9342
|
}
|
|
9274
9343
|
}
|
|
9275
|
-
const conflictsFile =
|
|
9344
|
+
const conflictsFile = path46.join(projectRoot, ".cleargate", ".conflicts.json");
|
|
9276
9345
|
let data;
|
|
9277
9346
|
try {
|
|
9278
9347
|
const raw = await fsPromises11.readFile(conflictsFile, "utf8");
|
|
@@ -9360,8 +9429,8 @@ function formatEntry(entry) {
|
|
|
9360
9429
|
|
|
9361
9430
|
// src/commands/admin-login.ts
|
|
9362
9431
|
init_cjs_shims();
|
|
9363
|
-
var
|
|
9364
|
-
var
|
|
9432
|
+
var fs36 = __toESM(require("fs"), 1);
|
|
9433
|
+
var path47 = __toESM(require("path"), 1);
|
|
9365
9434
|
var os7 = __toESM(require("os"), 1);
|
|
9366
9435
|
var DEFAULT_MCP_URL = "http://localhost:3000";
|
|
9367
9436
|
function resolveMcpUrl(mcpUrlFlag, env) {
|
|
@@ -9370,14 +9439,14 @@ function resolveMcpUrl(mcpUrlFlag, env) {
|
|
|
9370
9439
|
function resolveAuthFilePath(opts) {
|
|
9371
9440
|
if (opts.authFilePath) return opts.authFilePath;
|
|
9372
9441
|
const homedirFn = opts.homedir ?? os7.homedir;
|
|
9373
|
-
return
|
|
9442
|
+
return path47.join(homedirFn(), ".cleargate", "admin-auth.json");
|
|
9374
9443
|
}
|
|
9375
9444
|
function writeAdminAuth(filePath, token) {
|
|
9376
|
-
const dir =
|
|
9377
|
-
|
|
9445
|
+
const dir = path47.dirname(filePath);
|
|
9446
|
+
fs36.mkdirSync(dir, { recursive: true });
|
|
9378
9447
|
const payload = JSON.stringify({ version: 1, token }, null, 2);
|
|
9379
|
-
|
|
9380
|
-
|
|
9448
|
+
fs36.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
|
|
9449
|
+
fs36.chmodSync(filePath, 384);
|
|
9381
9450
|
}
|
|
9382
9451
|
async function adminLoginHandler(opts = {}) {
|
|
9383
9452
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
@@ -9487,8 +9556,8 @@ async function adminLoginHandler(opts = {}) {
|
|
|
9487
9556
|
|
|
9488
9557
|
// src/commands/hotfix.ts
|
|
9489
9558
|
init_cjs_shims();
|
|
9490
|
-
var
|
|
9491
|
-
var
|
|
9559
|
+
var fs37 = __toESM(require("fs"), 1);
|
|
9560
|
+
var path48 = __toESM(require("path"), 1);
|
|
9492
9561
|
function defaultExit4(code) {
|
|
9493
9562
|
return process.exit(code);
|
|
9494
9563
|
}
|
|
@@ -9498,7 +9567,7 @@ function maxHotfixId(pendingDir) {
|
|
|
9498
9567
|
let max = 0;
|
|
9499
9568
|
let entries;
|
|
9500
9569
|
try {
|
|
9501
|
-
entries =
|
|
9570
|
+
entries = fs37.readdirSync(pendingDir);
|
|
9502
9571
|
} catch {
|
|
9503
9572
|
return 0;
|
|
9504
9573
|
}
|
|
@@ -9512,13 +9581,13 @@ function maxHotfixId(pendingDir) {
|
|
|
9512
9581
|
return max;
|
|
9513
9582
|
}
|
|
9514
9583
|
function countActiveHotfixes(repoRoot) {
|
|
9515
|
-
const pendingDir =
|
|
9516
|
-
const archiveDir =
|
|
9584
|
+
const pendingDir = path48.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
9585
|
+
const archiveDir = path48.join(repoRoot, ".cleargate", "delivery", "archive");
|
|
9517
9586
|
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
9518
9587
|
let count = 0;
|
|
9519
9588
|
let pendingEntries = [];
|
|
9520
9589
|
try {
|
|
9521
|
-
pendingEntries =
|
|
9590
|
+
pendingEntries = fs37.readdirSync(pendingDir);
|
|
9522
9591
|
} catch {
|
|
9523
9592
|
}
|
|
9524
9593
|
for (const entry of pendingEntries) {
|
|
@@ -9526,13 +9595,13 @@ function countActiveHotfixes(repoRoot) {
|
|
|
9526
9595
|
}
|
|
9527
9596
|
let archiveEntries = [];
|
|
9528
9597
|
try {
|
|
9529
|
-
archiveEntries =
|
|
9598
|
+
archiveEntries = fs37.readdirSync(archiveDir);
|
|
9530
9599
|
} catch {
|
|
9531
9600
|
}
|
|
9532
9601
|
for (const entry of archiveEntries) {
|
|
9533
9602
|
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
|
|
9534
9603
|
try {
|
|
9535
|
-
const stat =
|
|
9604
|
+
const stat = fs37.statSync(path48.join(archiveDir, entry));
|
|
9536
9605
|
if (stat.mtimeMs >= sevenDaysAgo) count++;
|
|
9537
9606
|
} catch {
|
|
9538
9607
|
}
|
|
@@ -9541,7 +9610,7 @@ function countActiveHotfixes(repoRoot) {
|
|
|
9541
9610
|
return count;
|
|
9542
9611
|
}
|
|
9543
9612
|
function resolveTemplatePath(repoRoot) {
|
|
9544
|
-
return
|
|
9613
|
+
return path48.join(repoRoot, ".cleargate", "templates", "hotfix.md");
|
|
9545
9614
|
}
|
|
9546
9615
|
function hotfixNewHandler(opts, cli) {
|
|
9547
9616
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -9560,14 +9629,14 @@ function hotfixNewHandler(opts, cli) {
|
|
|
9560
9629
|
);
|
|
9561
9630
|
return exitFn(1);
|
|
9562
9631
|
}
|
|
9563
|
-
const pendingDir =
|
|
9632
|
+
const pendingDir = path48.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
9564
9633
|
const maxId = maxHotfixId(pendingDir);
|
|
9565
9634
|
const nextId = maxId + 1;
|
|
9566
9635
|
const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
|
|
9567
9636
|
const templatePath = resolveTemplatePath(repoRoot);
|
|
9568
9637
|
let templateContent;
|
|
9569
9638
|
try {
|
|
9570
|
-
templateContent =
|
|
9639
|
+
templateContent = fs37.readFileSync(templatePath, "utf8");
|
|
9571
9640
|
} catch {
|
|
9572
9641
|
stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
|
|
9573
9642
|
return exitFn(2);
|
|
@@ -9575,10 +9644,10 @@ function hotfixNewHandler(opts, cli) {
|
|
|
9575
9644
|
const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
|
|
9576
9645
|
const fileSlug = opts.slug.replace(/-/g, "_");
|
|
9577
9646
|
const fileName = `${idStr}_${fileSlug}.md`;
|
|
9578
|
-
const outPath =
|
|
9647
|
+
const outPath = path48.join(pendingDir, fileName);
|
|
9579
9648
|
try {
|
|
9580
|
-
|
|
9581
|
-
|
|
9649
|
+
fs37.mkdirSync(pendingDir, { recursive: true });
|
|
9650
|
+
fs37.writeFileSync(outPath, content, "utf8");
|
|
9582
9651
|
} catch (err) {
|
|
9583
9652
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9584
9653
|
stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
|
|
@@ -9588,6 +9657,218 @@ function hotfixNewHandler(opts, cli) {
|
|
|
9588
9657
|
return exitFn(0);
|
|
9589
9658
|
}
|
|
9590
9659
|
|
|
9660
|
+
// src/commands/mcp-serve.ts
|
|
9661
|
+
init_cjs_shims();
|
|
9662
|
+
var readline5 = __toESM(require("readline"), 1);
|
|
9663
|
+
init_config();
|
|
9664
|
+
init_factory();
|
|
9665
|
+
|
|
9666
|
+
// src/auth/refresh.ts
|
|
9667
|
+
init_cjs_shims();
|
|
9668
|
+
async function refreshAccessToken(baseUrl, refreshToken, deps = {}) {
|
|
9669
|
+
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
9670
|
+
const res = await fetchFn(`${baseUrl}/auth/refresh`, {
|
|
9671
|
+
method: "POST",
|
|
9672
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
9673
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
9674
|
+
});
|
|
9675
|
+
if (!res.ok) {
|
|
9676
|
+
const body = await res.json().catch(() => ({}));
|
|
9677
|
+
throw new RefreshError(res.status, body.error ?? "unknown_error");
|
|
9678
|
+
}
|
|
9679
|
+
const json = await res.json();
|
|
9680
|
+
if (typeof json.access_token !== "string" || typeof json.refresh_token !== "string" || typeof json.expires_in !== "number") {
|
|
9681
|
+
throw new RefreshError(500, "malformed_response");
|
|
9682
|
+
}
|
|
9683
|
+
return json;
|
|
9684
|
+
}
|
|
9685
|
+
var RefreshError = class extends Error {
|
|
9686
|
+
constructor(status, code) {
|
|
9687
|
+
super(`refresh failed: ${status} ${code}`);
|
|
9688
|
+
this.status = status;
|
|
9689
|
+
this.code = code;
|
|
9690
|
+
this.name = "RefreshError";
|
|
9691
|
+
}
|
|
9692
|
+
status;
|
|
9693
|
+
code;
|
|
9694
|
+
};
|
|
9695
|
+
var AuthFetcher = class {
|
|
9696
|
+
constructor(opts) {
|
|
9697
|
+
this.opts = opts;
|
|
9698
|
+
}
|
|
9699
|
+
opts;
|
|
9700
|
+
accessToken = null;
|
|
9701
|
+
accessExpiresAt = 0;
|
|
9702
|
+
inflight = null;
|
|
9703
|
+
/** Returns a fresh access token, refreshing if needed. */
|
|
9704
|
+
async getAccessToken() {
|
|
9705
|
+
const now = (this.opts.now ?? (() => Date.now()))();
|
|
9706
|
+
const skewMs = (this.opts.skewSeconds ?? 60) * 1e3;
|
|
9707
|
+
if (this.accessToken && now < this.accessExpiresAt - skewMs) {
|
|
9708
|
+
return this.accessToken;
|
|
9709
|
+
}
|
|
9710
|
+
if (this.inflight) return this.inflight;
|
|
9711
|
+
this.inflight = this.refreshNow().finally(() => {
|
|
9712
|
+
this.inflight = null;
|
|
9713
|
+
});
|
|
9714
|
+
return this.inflight;
|
|
9715
|
+
}
|
|
9716
|
+
/** Force the next call to refresh. Used after a 401. */
|
|
9717
|
+
invalidate() {
|
|
9718
|
+
this.accessToken = null;
|
|
9719
|
+
this.accessExpiresAt = 0;
|
|
9720
|
+
}
|
|
9721
|
+
async refreshNow() {
|
|
9722
|
+
const stored = await this.opts.loadRefresh();
|
|
9723
|
+
if (!stored) {
|
|
9724
|
+
throw new RefreshError(401, "no_refresh_token");
|
|
9725
|
+
}
|
|
9726
|
+
const exchanged = await refreshAccessToken(this.opts.baseUrl, stored, {
|
|
9727
|
+
...this.opts.fetch ? { fetch: this.opts.fetch } : {},
|
|
9728
|
+
...this.opts.now ? { now: this.opts.now } : {}
|
|
9729
|
+
});
|
|
9730
|
+
await this.opts.saveRefresh(exchanged.refresh_token);
|
|
9731
|
+
const now = (this.opts.now ?? (() => Date.now()))();
|
|
9732
|
+
this.accessToken = exchanged.access_token;
|
|
9733
|
+
this.accessExpiresAt = now + exchanged.expires_in * 1e3;
|
|
9734
|
+
return exchanged.access_token;
|
|
9735
|
+
}
|
|
9736
|
+
};
|
|
9737
|
+
|
|
9738
|
+
// src/commands/mcp-serve.ts
|
|
9739
|
+
var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
|
|
9740
|
+
async function mcpServeHandler(opts) {
|
|
9741
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
9742
|
+
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
9743
|
+
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
9744
|
+
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
9745
|
+
const cfg = loadConfig({
|
|
9746
|
+
flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
|
|
9747
|
+
});
|
|
9748
|
+
const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
|
|
9749
|
+
const store = await (opts.createStore ?? createTokenStore)({
|
|
9750
|
+
...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
|
|
9751
|
+
...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
|
|
9752
|
+
});
|
|
9753
|
+
const fetcher = new AuthFetcher({
|
|
9754
|
+
baseUrl,
|
|
9755
|
+
loadRefresh: () => store.load(opts.profile),
|
|
9756
|
+
saveRefresh: (t) => store.save(opts.profile, t),
|
|
9757
|
+
...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
|
|
9758
|
+
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
9759
|
+
});
|
|
9760
|
+
try {
|
|
9761
|
+
await fetcher.getAccessToken();
|
|
9762
|
+
} catch (err) {
|
|
9763
|
+
if (err instanceof RefreshError) {
|
|
9764
|
+
stderr(
|
|
9765
|
+
`cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
|
|
9766
|
+
`
|
|
9767
|
+
);
|
|
9768
|
+
} else {
|
|
9769
|
+
stderr(
|
|
9770
|
+
`cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
|
|
9771
|
+
`
|
|
9772
|
+
);
|
|
9773
|
+
}
|
|
9774
|
+
return exit(1);
|
|
9775
|
+
}
|
|
9776
|
+
const inputStream = opts.stdin ?? process.stdin;
|
|
9777
|
+
const rl = readline5.createInterface({
|
|
9778
|
+
input: inputStream,
|
|
9779
|
+
output: void 0,
|
|
9780
|
+
terminal: false
|
|
9781
|
+
});
|
|
9782
|
+
for await (const line of rl) {
|
|
9783
|
+
if (!line.trim()) continue;
|
|
9784
|
+
try {
|
|
9785
|
+
await proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr);
|
|
9786
|
+
} catch (err) {
|
|
9787
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9788
|
+
stderr(`cleargate mcp serve: proxy error: ${errMsg}
|
|
9789
|
+
`);
|
|
9790
|
+
const id = extractId(line);
|
|
9791
|
+
if (id !== void 0) {
|
|
9792
|
+
stdout(
|
|
9793
|
+
JSON.stringify({
|
|
9794
|
+
jsonrpc: "2.0",
|
|
9795
|
+
id,
|
|
9796
|
+
error: { code: -32603, message: `proxy error: ${errMsg}` }
|
|
9797
|
+
}) + "\n"
|
|
9798
|
+
);
|
|
9799
|
+
}
|
|
9800
|
+
}
|
|
9801
|
+
}
|
|
9802
|
+
}
|
|
9803
|
+
async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
|
|
9804
|
+
let parsed;
|
|
9805
|
+
try {
|
|
9806
|
+
parsed = JSON.parse(line);
|
|
9807
|
+
} catch {
|
|
9808
|
+
stderr(`cleargate mcp serve: ignoring non-JSON line: ${line.slice(0, 80)}
|
|
9809
|
+
`);
|
|
9810
|
+
return;
|
|
9811
|
+
}
|
|
9812
|
+
const isNotification = !("id" in parsed) || parsed.id === void 0 || parsed.id === null;
|
|
9813
|
+
let access = await fetcher.getAccessToken();
|
|
9814
|
+
let res = await postFrame(baseUrl, line, access, fetchFn);
|
|
9815
|
+
if (res.status === 401) {
|
|
9816
|
+
fetcher.invalidate();
|
|
9817
|
+
access = await fetcher.getAccessToken();
|
|
9818
|
+
res = await postFrame(baseUrl, line, access, fetchFn);
|
|
9819
|
+
}
|
|
9820
|
+
if (isNotification) {
|
|
9821
|
+
await res.arrayBuffer().catch(() => void 0);
|
|
9822
|
+
return;
|
|
9823
|
+
}
|
|
9824
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
9825
|
+
if (ct.includes("text/event-stream")) {
|
|
9826
|
+
await streamSse(res, stdout);
|
|
9827
|
+
} else {
|
|
9828
|
+
const text = await res.text();
|
|
9829
|
+
if (text.length > 0) stdout(text + "\n");
|
|
9830
|
+
}
|
|
9831
|
+
}
|
|
9832
|
+
async function postFrame(baseUrl, body, accessToken, fetchFn) {
|
|
9833
|
+
return fetchFn(`${baseUrl}/mcp`, {
|
|
9834
|
+
method: "POST",
|
|
9835
|
+
headers: {
|
|
9836
|
+
"Content-Type": "application/json",
|
|
9837
|
+
Accept: "application/json, text/event-stream",
|
|
9838
|
+
Authorization: `Bearer ${accessToken}`
|
|
9839
|
+
},
|
|
9840
|
+
body
|
|
9841
|
+
});
|
|
9842
|
+
}
|
|
9843
|
+
async function streamSse(res, stdout) {
|
|
9844
|
+
if (!res.body) return;
|
|
9845
|
+
const reader = res.body.getReader();
|
|
9846
|
+
const decoder = new TextDecoder("utf-8");
|
|
9847
|
+
let buf = "";
|
|
9848
|
+
for (; ; ) {
|
|
9849
|
+
const { value, done } = await reader.read();
|
|
9850
|
+
if (done) break;
|
|
9851
|
+
buf += decoder.decode(value, { stream: true });
|
|
9852
|
+
let nl;
|
|
9853
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
9854
|
+
const ln = buf.slice(0, nl);
|
|
9855
|
+
buf = buf.slice(nl + 1);
|
|
9856
|
+
if (ln.startsWith("data:")) {
|
|
9857
|
+
const payload = ln.slice(5).trim();
|
|
9858
|
+
if (payload) stdout(payload + "\n");
|
|
9859
|
+
}
|
|
9860
|
+
}
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
function extractId(line) {
|
|
9864
|
+
try {
|
|
9865
|
+
const obj = JSON.parse(line);
|
|
9866
|
+
return "id" in obj ? obj.id : void 0;
|
|
9867
|
+
} catch {
|
|
9868
|
+
return void 0;
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9591
9872
|
// src/cli.ts
|
|
9592
9873
|
var program = new import_commander.Command();
|
|
9593
9874
|
program.name("cleargate").description("ClearGate CLI \u2014 connects AI agent teams to the ClearGate MCP server").version(package_default.version, "-V, --version").option("--profile <name>", "configuration profile to use", "default").option("--mcp-url <url>", "MCP server URL (overrides config file and env)").showHelpAfterError("(use `cleargate --help`)");
|
|
@@ -9830,5 +10111,13 @@ var hotfix = program.command("hotfix").description("hotfix lane commands (off-sp
|
|
|
9830
10111
|
hotfix.command("new <slug>").description("scaffold a new HOTFIX-NNN_<slug>.md in pending-sync/").action((slug) => {
|
|
9831
10112
|
hotfixNewHandler({ slug });
|
|
9832
10113
|
});
|
|
10114
|
+
var mcp = program.command("mcp").description("MCP-server bridge commands (stdio shim, registration helpers)");
|
|
10115
|
+
mcp.command("serve").description("run a stdio MCP server that proxies to the cleargate HTTP /mcp endpoint with auto-refresh Bearer auth").action(async (_opts, command) => {
|
|
10116
|
+
const globals = command.parent.parent.opts();
|
|
10117
|
+
await mcpServeHandler({
|
|
10118
|
+
profile: globals.profile,
|
|
10119
|
+
...globals.mcpUrl !== void 0 ? { mcpUrlFlag: globals.mcpUrl } : {}
|
|
10120
|
+
});
|
|
10121
|
+
});
|
|
9833
10122
|
void program.parseAsync(process.argv);
|
|
9834
10123
|
//# sourceMappingURL=cli.cjs.map
|